
Gutartige Offline-Reproduktion von CVE-2026-93355 — LiteLLM-Kontoübernahme per JWT mit unbestätigter E-Mail (führt den echten LiteLLM-Code aus)
Ein eigenständiges, wegwerfbares Labor, das
CVE-2026-93355 reproduziert: Die
JWT-Authentifizierung von LiteLLM
ermittelt die Identität eines Aufrufers, indem sie auf einen E-Mail-Abgleich
zurückfällt — und niemals den email_verified-Claim prüft. Ein Angreifer, der
sein eigenes Identity-Provider-Konto dazu bringen kann, ein gültig signiertes Token
mit der E-Mail-Adresse eines Opfers (unverifiziert) auszustellen, erbt die Rolle des
Opfers, bis hin zu proxy_admin, und der Abgleich überschreibt stillschweigend
die gespeicherte SSO-Bindung des Opfers mit dem Subject des Angreifers, wodurch
die Übernahme persistent wird.
| Advisory | VulnCheck · OX Security |
| CVE | CVE-2026-93355 |
| Projekt | litellm (Proxy / JWT-Auth) |
| Betroffen | ≤ 1.102.1 laut Advisory — weiterhin ungepatcht: Der verwundbare Code ist in 1.104.0 (letzte stabile Version) und in main mit Stand 2026-10-07 unverändert |
| CVSS | 8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N) |
| Schwäche | CWE-1390 — schwache Authentifizierung |
| Ursache | E-Mail-basierte Benutzersuche wird vertraut, ohne email_verified zu prüfen |
| Entdeckung | Nir Zadok & Moshe Siman Tov Bustan (OX Security) |
Fix-Status (2026-10-07): Es existiert noch keine korrigierte Version. Das Advisory nennt
1.102.1nur, weil dies die neueste Version zum Zeitpunkt der Offenlegung war; LiteLLM hat seitdem1.103.xund1.104.0veröffentlicht, und_get_fuzzy_user_objectin beiden — sowie inmain— hat weiterhin keineemail_verified-Prüfung. Das Labor pinnt1.102.1für die Reproduzierbarkeit;scripts/verify_against_real_source.pywird den Tag melden, an dem ein Fix erscheint.
Dies reproduziert eine öffentlich offengelegte Schwachstelle zu Lehr- und Verteidigungszwecken. Es läuft vollständig lokal und berührt keinen echten Dienst:
[email protected]-Admin-Zeile innerhalb dieses Prozesses auf und gibt sie aus.
Es gibt kein echtes Konto, keinen echten Schlüssel, und nichts verlässt deinen
Rechner.Der LiteLLM-Proxy ermöglicht die Authentifizierung mit einem JWT von deinem
konfigurierten Identity Provider. Um dieses Token einem LiteLLM-Benutzer
zuzuordnen, ruft er get_user_object() auf, das zunächst das Subject des Tokens
(sso_user_id) versucht und bei einem Fehlschlag auf _get_fuzzy_user_object()
zurückfällt — einen groß-/kleinschreibungsunabhängigen Abgleich des
email-Claims. Wer diese E-Mail in LiteLLMs Tabelle besitzt, zu dem wirst du.
Der email_verified-Claim des Tokens wird niemals gelesen — weder bei der
Claim-Extraktion (JWTHandler.get_user_email) noch bei der Auflösung. Jeder IdP,
der ein Token für eine unverifizierte E-Mail ausstellt (die meisten tun dies per
Design — die Verifizierung ist Aufgabe der vertrauenden Partei), erlaubt es einem
Angreifer, seine Profil-E-Mail auf die eines Opfers zu setzen, das Token
vorzulegen und die Rolle des Opfers zu erben. Schlimmer noch: Bei einem
E-Mail-Abgleich startet LiteLLM eine Hintergrundaufgabe, die die sso_user_id
des Opfers mit dem Subject des Angreifers überschreibt — sodass jede spätere
Anmeldung direkt zum Konto des Opfers führt, ohne dass eine E-Mail nötig ist, und
auch das Ändern der E-Mail nicht hilft.

Alles, was getestet wird, ist LiteLLMs eigener Code, importiert aus dem
gepinnten Paket litellm==1.102.1. Das Labor liefert nur die zwei Dinge, die es
liefern muss: eine gefälschte In-Memory-Benutzertabelle und ein Mock-IdP-Schlüsselpaar.
Es implementiert die verwundbare Logik nicht neu.
inspect.getsource() auf dem installierten _get_fuzzy_user_object und JWTHandler.get_user_email auf und stellen sicher, dass der E-Mail-Fallback existiert, die sso_user_id-Neubindung existiert und keine der beiden Funktionen email_verified referenziert. → scripts/verify_against_real_source.py_get_fuzzy_user_object geschickt; es wird auf die proxy_admin-Zeile des Opfers aufgelöst. → poc/01_account_takeover.pysso_user_id des Opfers an das Subjekt des Angreifers neu; ein späterer Login ganz ohne E-Mail wird direkt auf Admin aufgelöst, und „die doppelte E-Mail korrigieren“ widerruft dies nicht. → poc/02_persistent_rebind.pyemail_verified vor dem E-Mail-Fallback blockiert den Angriff, während verifizierte SSO-Logins weiterhin funktionieren. → poc/03_patched_path.py./run.sh
run.sh erstellt eine temporäre .venv, installiert litellm[proxy]==1.102.1, führt die
Ground-Truth-Prüfung aus, dann alle drei PoCs. Der erste Lauf lädt LiteLLM herunter und dauert
einige Minuten; lösche .venv, um aufzuräumen. Um einen vorhandenen Interpreter zu verwenden:
PYTHON=python3.12 ./run.sh.
Führe einen einzelnen PoC direkt aus (innerhalb der venv):
.venv/bin/python poc/01_account_takeover.py
cve-2026-93355-lab/
├── mock_idp.py # ephemeral RS256 OIDC IdP (real signatures)
├── fake_litellm_db.py # in-memory stand-in for the Prisma user table
├── harness.py # configures & drives LiteLLM's real objects
├── poc/
│ ├── 01_account_takeover.py # unverified email -> victim's proxy_admin
│ ├── 02_persistent_rebind.py # sso_user_id overwrite -> persistent access
│ └── 03_patched_path.py # the email_verified guard that closes it
├── scripts/
│ └── verify_against_real_source.py # proves the PoCs run the real vulnerable code
├── assets/attack-flow.svg|.png # the diagram above
├── blog-cve-2026-93355.md # the guard0 write-up
└── run.sh
email_verified: true erzwingen an der Vertrauensgrenze, bevor ein Token
die Identitätsauflösung erreicht; unverifizierte E-Mails ablehnen oder ignorieren.user_id_jwt_field),
nicht an eine veränderliche E-Mail, und nicht zulassen, dass der E-Mail-Fallback sie stillschweigend neu bindet.proxy_admin-Zeile ist ein dauerhaftes Übernahme-Primitiv.