Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-93355-lab — Benign, offline reproduction of CVE-2026-93355 — LiteLLM unverified-email JWT account takeover (runs LiteLLM's real code) | Kitploit
Tools/GitHubGitHub/inertfluid/cve-2026-93355-lab
Privilege EscalationVulnerability AnalysisWeb Application ExploitationIdentity & Access Management (IAM)AuthenticationPapers & ResearchLearning & EducationLabs & Practice
GitHubinertfluid/cve-2026-93355-lab

cve-2026-93355-lab

Benign, offline reproduction of CVE-2026-93355 — LiteLLM unverified-email JWT account takeover (runs LiteLLM's real code)

29 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository

CVE-2026-93355 — LiteLLM unverified-email JWT account takeover lab

A self-contained, disposable lab that reproduces CVE-2026-93355: LiteLLM's JWT authentication resolves a caller's identity by falling back to an email match — and never checks the email_verified claim. An attacker who can make their own identity-provider account emit a validly-signed token carrying a victim's email address (unverified) inherits the victim's role, up to proxy_admin, and the match silently rewrites the victim's stored SSO binding to the attacker's subject, making the takeover persistent.

AdvisoryVulnCheck · OX Security
CVECVE-2026-93355
Projectlitellm (proxy / JWT auth)
Affected≤ 1.102.1 per the advisory — still unpatched: the vulnerable code is unchanged in 1.104.0 (latest stable) and main as of 2026-10-07
CVSS8.1 HIGH (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)
WeaknessCWE-1390 — weak authentication
Root causeEmail-based user lookup trusted without verifying email_verified
DiscoveryNir Zadok & Moshe Siman Tov Bustan (OX Security)

Fix status (2026-10-07): no fixed release exists yet. The advisory lists 1.102.1 only because it was the newest version at disclosure; LiteLLM has since shipped 1.103.x and 1.104.0, and _get_fuzzy_user_object in both — and on main — still has no email_verified check. The lab pins 1.102.1 for reproducibility; scripts/verify_against_real_source.py will flag the day a fix lands.

⚠️ Ethical use

This reproduces a publicly disclosed vulnerability for educational and defensive purposes. It runs entirely locally and touches no real service:

  • There is no network egress. The "identity provider" is an in-process mock that mints a real RS256-signed JWT from an ephemeral keypair. The "database" is a Python list.
  • The exploit is benign: it resolves a seeded fake [email protected] admin row inside this process and prints it. There is no real account, no real key, and nothing leaves your machine.
  • Never point the technique at an IdP, tenant, or LiteLLM deployment you do not own and have permission to test.

The bug in one paragraph

LiteLLM's proxy lets you authenticate with a JWT from your configured identity provider. To map that token to a LiteLLM user it calls get_user_object(), which tries the token's subject (sso_user_id) and, on a miss, falls back to _get_fuzzy_user_object() — a case-insensitive match on the email claim. Whoever owns that email in LiteLLM's table is who you become. The token's email_verified claim is never read — not during claim extraction (JWTHandler.get_user_email) and not during resolution. So any IdP that will issue a token for an unverified email (most will, by design — verification is the relying party's job) lets an attacker set their profile email to a victim's, present the token, and inherit the victim's role. Worse, on an email match LiteLLM fires a background task that overwrites the victim's sso_user_id with the attacker's subject — so every later login resolves straight to the victim's account with no email needed, and rotating the email doesn't help.

Attack flow: vulnerable vs. fixed

What this lab proves — and how it stays honest

Everything under test is LiteLLM's own code, imported from the pinned litellm==1.102.1 package. The lab supplies only two things it must: a fake in-memory user table and a mock IdP keypair. It does not re-implement the vulnerable logic.

  1. Ground truth. We inspect.getsource() the installed _get_fuzzy_user_object and JWTHandler.get_user_email and assert the email fallback exists, the sso_user_id rebind exists, and neither function references email_verified. → scripts/verify_against_real_source.py
  2. Account takeover. A valid token with an unverified victim email is run through LiteLLM's real claim extraction and real _get_fuzzy_user_object; it resolves to the victim's proxy_admin row. → poc/01_account_takeover.py
  3. Persistence. The email match's background write rebinds the victim's sso_user_id to the attacker's subject; a later login with no email at all resolves directly to admin, and "fix the duplicate email" does not revoke it. → poc/02_persistent_rebind.py
  4. The fix. Enforcing email_verified before the email fallback blocks the attack while leaving verified SSO logins working. → poc/03_patched_path.py

Run it

./run.sh

run.sh builds a throwaway .venv, installs litellm[proxy]==1.102.1, runs the ground-truth check, then all three PoCs. First run downloads LiteLLM and takes a few minutes; delete .venv to clean up. To use an existing interpreter: PYTHON=python3.12 ./run.sh.

Run a single PoC directly (inside the venv):

.venv/bin/python poc/01_account_takeover.py

Layout

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

Remediation

  • Upgrade once LiteLLM ships a fix, and track the advisory.
  • Enforce email_verified: true at the trust boundary before any token reaches identity resolution; reject or ignore unverified emails.
  • Bind identity to a stable, provider-issued subject (user_id_jwt_field), not a mutable email, and don't let the email fallback silently rebind it.
  • Don't pre-provision privileged roles by email alone. An email-keyed proxy_admin row is a standing takeover primitive.

References

  1. CVE-2026-93355 (NVD) — https://nvd.nist.gov/vuln/detail/CVE-2026-93355
  2. VulnCheck advisory — https://www.vulncheck.com/advisories/litellm-weak-jwt-authentication-via-email-based-user-lookup
  3. OX Security write-up — https://www.ox.security/blog/litellm-an-ordinary-login-token-can-become-someone-elses-admin-account/
  4. LiteLLM — https://github.com/BerriAI/litellm
Download Tool