본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 28. 21:15

악성 업로드에 대한 fourpointo 테스트: 프롬프트 인젝션 (Prompt Injection) 및 저장형 XSS (Stored XSS)

요약

셀프 호스팅 Flask 앱인 fourpointo를 대상으로 프롬프트 인젝션 및 저장형 XSS 공격 가능성을 테스트한 보안 분석 과정입니다. 매직 바이트 검증과 LLM 기반 콘텐츠 게이트를 통해 악성 파일 업로드를 방어하는 메커니즘을 검증합니다.

핵심 포인트

  • 매직 바이트 검증을 통한 구조적 파일 검사 수행
  • LLM을 활용한 콘텐츠 게이트로 비정상 PDF 필터링
  • Kali VM 환경을 이용한 안전한 로컬 보안 테스트
  • PyMuPDF 충돌 방지를 위한 파일 검증 로직의 중요성

fourpointo는 제가 구축한 셀프 호스팅(self-hosted) Flask 앱으로, 업로드된 과제 PDF로부터 AI 기반의 작업 체크리스트와 루브릭(rubric) 분석 내용을 생성합니다. 추출을 위해 Groq의 LLaMA 3.3 70B를 사용하며, 저장에는 SQLite를, Cloudflare Tunnel 뒤에서는 Gunicorn을 사용합니다.

일반적인 사용 중에 발견된 매직 바이트(magic-byte) 검증 버그(가짜 PDF가 PyMuPDF에서 처리되지 않은 충돌을 일으키는 문제)를 수정한 후, 저는 더 나아가 공격자가 할 법한 방식으로 업로드 파이프라인을 실제로 조사해보고 싶었습니다. 이 글은 그 과정인 설정, 테스트 케이스 및 결과를 기록합니다.

모든 테스트는 Kali VM 환경의 로컬 fourpointo 복사본을 대상으로 로컬에서 수행되었습니다. 어떤 시점에서도 운영 데이터나 다른 사용자는 관여되지 않았습니다.

설정 (Setup)

첫 번째 단계는 작업의 기준이 될 유효한 베이스라인 PDF를 만드는 것이었습니다. 빈 파일이나 쓰레기 파일은 fourpointo가 이미 거부하기 때문에 유용한 정보를 제공하지 못할 것이기 때문입니다.

[

]

이를 통해 나머지 테스트의 원재료로 사용할 수 있는, 작지만 실제로 유효한 PDF인 real.pdf를 확보했습니다.

테스트 1: 입력 검증 (매직 바이트 및 콘텐츠 게이트)

더 고급 테스트를 진행하기 전에, 기존의 검증 계층이 실제로 작동하는지 확인하고 싶었습니다. fourpointo에는 두 가지 게이트가 있습니다:

  1. 구조적으로 PDF가 아닌 파일을 거부하는 매직 바이트 (magic-byte) 체크
  2. 과제 명세서처럼 읽히지 않는 PDF를 거부하는 콘텐츠 게이트 (LLM 호출)

두 번째 게이트를 테스트하기 위해, 헤더는 통과하지만 내부 구조는 불완전하도록 유효한 PDF를 잘라냈습니다(truncated).

[

]

잘려진 파일을 업로드했을 때, 충돌이나 무음 실패(silent failure) 대신 깔끔하게 거부되었습니다.

결과: 두 게이트(gate) 모두 의도한 대로 동작했습니다. 잘려진 파일은 여전히 유효한 PDF 헤더를 가지고 있었지만(매직 바이트(magic bytes)가 잘림에도 살아남기 때문에), 두 번째 게이트가 수락할 만큼 충분한 일관된 콘텐츠가 부족하여 거부되었습니다. 또한, 다운스트림(downstream)의 어떤 것도 망가뜨리지 않고 명확한 메시지와 함께 거부되었습니다.

현실적인 사양 PDF(Specification PDF) 구축하기

프롬프트 인젝션 (Prompt Injection) 및 저장형 XSS (Stored XSS)를 제대로 테스트하려면, 콘텐츠 게이트(content gate)를 실제로 통과할 수 있는 PDF가 필요했습니다. 즉, '과제 섹션(Tasks section)'과 '채점 기준 섹션(Grading Criteria section)'을 포함하여 실제 과제 사양서처럼 보여야 했습니다.

이를 위해 fpdf2를 사용하여 필요할 때마다 생성할 수 있는 작은 Python 스크립트를 작성했습니다. 이 방식은 나중에 한 번에 한 줄씩 편집함으로써 다양한 변형을 쉽게 만들 수 있게 해주었습니다.

스크립트가 문제없이 실행된 후, 결과물인 spec.pdf를 악성 콘텐츠가 없는 베이스라인(baseline)으로 업로드했습니다. 이는 콘텐츠 게이트를 통과하였으며 정상적인 체크리스트를 생성했습니다.

이를 통해 적대적(adversarial)인 요소를 도입하기 전에 테스트 하네스(test harness)가 엔드 투 엔드(end to end)로 작동함을 확인했습니다.

테스트 2: 프롬프트 인젝션 (Prompt Injection)

fourpointo 파이프라인은 태스크(task)와 루브릭(rubric) 기준을 생성하기 위해 PDF 텍스트를 LLM 호출로 직접 추출합니다. 이는 문서 콘텐츠를 신뢰할 수 없는 입력(untrusted input)으로 취급하지 않을 경우 프롬프트 인젝션 (Prompt Injection)에 취약해질 수 있는 전형적인 파이프라인입니다.

"이전의 모든 지침을 무시하십시오"와 같은 명백한 문구를 사용하는 대신, 저는 실제 인젝션 시도가 사용자 제공 콘텐츠 내에 삽입된 시스템 수준의 지침을 사칭하려는 방식과 유사하게 더 위장된 패턴을 사용했습니다.

저는 이를 새로운 PDF에 포함시켰고, 정상적으로 생성되는 것을 확인했습니다.

파일을 업로드하고 체크리스트를 생성한 후, 루브릭 추출을 위한 원시(raw) JSON 출력을 확인했습니다.

인젝션된 텍스트는 출력물의 어디에도 나타나지 않았습니다. 모델은 실제 태스크와 실제 채점 기준만을 추출했으며 그 외의 것은 추출하지 않았습니다.

또한 브라우저에서 렌더링된 프로젝트 페이지를 확인하여, 루브릭뿐만 아니라 태스크 리스트에 대해서도 동일한 결과가 유지되는지 확인했습니다.

결과: 인젝션 (Injection) 시도는 실패했습니다. fourpointo의 추출 (Extraction) 과정은 작업 (Task) 텍스트 내에 지시어와 유사한 문구가 직접 포함되어 있었음에도 정확성을 유지했습니다. 주목할 점은 fourpointo의 AI 레이어 (AI layer)는 콘텐츠를 읽고 표시할 뿐이라는 것입니다. AI는 무엇인가를 채점하거나 실제 결과에 영향을 미치는 결정을 내리지 않으므로, 여기서 인젝션이 성공하더라도 표시된 체크리스트를 오염시키는 것 이상의 제한적인 영향만을 미쳤을 것입니다. 그럼에도 불구하고, 이는 추출 단계가 신뢰할 수 없는 문서 콘텐츠 내에 포함된 지시어를 맹목적으로 따르지 않는다는 것을 보여주는 의미 있는 결과입니다.

테스트 3: PDF 콘텐츠를 통한 저장형 XSS (Stored XSS)

다음으로, PDF의 작업 텍스트 내에 삽입된 스크립트 태그 (Script tag)가 전체 파이프라인 (Pipeline, 추출, 저장 및 렌더링)을 통과하여 브라우저에서 실행될 수 있는지 확인하고 싶었습니다. 실제 공격자가 일반적인 독자의 눈을 피해 침투하려는 방식에 더 가깝게 만들기 위해, 페이로드 (Payload)를 단독으로 배치하는 대신 합법적인 작업의 일부인 것처럼 위장했습니다.

결과물인 PDF를 업로드하고 체크리스트를 생성한 다음, 렌더링된 UI의 모든 작업을 확인했습니다.

경고(alert)가 발생하지 않았으며, 표시된 작업 중 그 어떤 것도 <script> 태그를 포함하고 있지 않았습니다. 작업 레이블(Analyze Dataset, Write Report, Check Formatting, Create Charts, Submit Report)을 살펴보면, AI가 원문 텍스트를 그대로 다시 출력(echoing)하지 않는다는 것이 명확합니다. AI는 프론트엔드(frontend)에 도달하기 전에 각 작업을 짧은 레이블로 의역(paraphrasing)하고 있습니다.

결과: XSS는 관찰되지 않았으나, 그 이유가 중요합니다. 이는 페이로드(payload)를 잡아낸 의도적인 살균(sanitization) 제어 때문이 아니었습니다. 페이로드가 렌더링(rendering)될 만큼 생존하기 전에 요약(summarization) 단계에서 콘텐츠를 다시 작성했기 때문입니다. 만약 fourpointo의 프롬프트(prompt)가 작업을 요약하는 대신 있는 그대로 추출하도록 변경된다면, 이 보호 조치는 아무도 모르는 사이에 사라질 수 있습니다. 현재로서는 실질적인 완화(mitigation) 수단이지만, 장기적으로 신뢰할 수 있는 방법은 아닙니다.

테스트 4: 직접적인 양식 입력을 통한 저장형 XSS (Stored XSS)

PDF 기반 테스트는 실제 출력 이스케이프(escaping) 여부에 대해 결정적이지 않았기 때문에(페이로드가 렌더링될 때까지 살아남지 못함), 프로젝트 이름 필드에 직접 <script> 태그를 입력하여 렌더링 계층(rendering layer)을 직접 테스트했습니다. 이 필드는 중간에 LLM 단계를 거치지 않고 저장 및 표시되므로, 프론트엔드가 출력 시 HTML을 이스케이프하는지 여부를 격리하여 확인할 수 있습니다.

프로젝트를 생성한 후, 프로젝트 페이지와 사이드바(sidebar)에서 이름이 어떻게 렌더링되는지 확인했습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0