LiteLLM comprometida en PyPI: el backdoor que corre aunque nunca importes el paquete

LiteLLM comprometida en PyPI: el backdoor que corre aunque nunca importes el paquete

LiteLLM comprometida en PyPI: el backdoor que corre aunque nunca importes el paquete

Si ayer hiciste pip install litellm o levaste un contenedor con la versión 1.107.0, detén todo y revisa tus llaves. Entre hoy en la mañana, alguien publicó en PyPI una versión comprometida de LiteLLM, el gateway open source que miles de empresas y self-hosters usan para hablar con OpenAI, Anthropic y el resto del zoo de proveedores. Y lo que metieron adentro es, técnicamente, un trabajo fino de ingeniería maliciosa.

El truco del .pth: malware sin importar nada

Lo que hace particularmente brutal a este ataque es dónde vive el bicho. Los siete wheels de la versión 1.107.0, para Linux, macOS y Windows, traen tres archivos en site-packages: cook.pth, cook.py y sitecustomize.py. El primero es una línea de texto, import cook, pero Python ejecuta cada .pth que encuentra en site-packages cada vez que arranca cualquier intérprete. No necesitas importar LiteLLM, ni usarla: cualquier proceso de Python en el host, desde pip hasta el language server de tu editor, activa el roba-credenciales.

El payload, bautizado privcollect, va a buscar todo lo que un admin como yo tiene en el disco: llaves SSH, credenciales de AWS, Azure y gcloud, configs de Kubernetes y Docker, historiales de shell, contraseñas de Chromium y Firefox, tokens de Claude Code, Cursor, Codex y Gemini CLI, llaves GPG, certificados TLS privados y hasta las variables de entorno de otros procesos leyendo /proc. Si en la máquina hay un proxy LiteLLM con su base de datos, extrae la tabla completa de llaves de proveedores.

La exfiltración, con sello de calidad

Nada de tráfico en claro: cifran todo con AES-256-CBC, envuelven la llave con RSA-4096 y mandan el paquete como un tar.gz con POST a www.litellm[.]dev, un dominio espejo del oficial que se camufla como tráfico legítimo. Encima, sitecustomize instala un daemon que cada hora lee la dirección de su servidor desde un archivo en un repo de GitHub y descarga y ejecuta lo que venga. Segunda etapa actualizable en vivo.

Cómo entró si el repo está limpio

Lo inquietante: el código malicioso no está en el tag v1.107.0 del repositorio. El único rastro es una línea inyectada en el pyproject.toml del paquete compilado, data = «cook», que copia la carpeta del bicho al instalar. Quien revisara source, sdist o el tag de git no veía nada. Todo apunta a un pipeline de release comprometido o un token de PyPI robado. Y en contenedores la cosa igual: la procedencia de build de las imágenes oficiales apunta a un repo camuflajeado, Berri-AI con guion, en vez de BerriAI. PyPI ya cuarentenó la versión, Socket la detectó siete minutos después de publicada, y la última versión limpia es la 1.106.2.

Esto es la segunda vez en 2026 que LiteLLM termina comprometida: en marzo el punto de entrada fue un escáner Trivy contaminado en su propio CI. Que tu herramienta de seguridad sea la puerta de entrada del ataque en un caso, y el pipeline de build en otro, dice bastante de dónde está el punto suave de la infraestructura de IA: no en los modelos, sino en los tubos.

Lo que hago yo con esto

Mi lección es simple y valiosa después de años administrando servidores: pip install es ejecutar código de alguien en tu máquina, igual que correr un script bajado de internet. Entonces: pinnéa versiones exactas, idealmente con hash; no actualices a lo loco un paquete que carga en producción el mismo día; y si tocó esta versión, asume todas las credenciales de ese host quemadas, desde las llaves SSH hasta los tokens de tus herramientas de IA, aunque nunca hayas abierto LiteLLM. El paquete, el repo y la imagen latest son tres superficies de ataque distintas, y atacantes al alza. Yo no confío ni en el primero.

Fuente de inspiración: Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor – SafeDep

Comentarios

Aún no hay comentarios. ¿Por qué no comienzas el debate?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *