Skip to main content

Cómo un escáner de seguridad comprometido se convirtió en la clave para insertar una puerta trasera en LiteLLM

Escrito por
illustration hero ai

24 de marzo de 2026

0 minutos de lectura

El 24 de marzo de 2026, se descubrió que dos versiones del paquete de Python litellm en PyPI contenían código malicioso. Las versiones 1.82.7 y 1.82.8 fueron publicadas por un actor de amenazas conocido como TeamPCP, que obtuvo las credenciales de PyPI del responsable del paquete mediante un compromiso previo de Trivy, un escáner de seguridad de código abierto que se usa en el pipeline de CI/CD de LiteLLM.

Las versiones maliciosas estuvieron disponibles durante aproximadamente tres horas, hasta que PyPI puso el paquete en cuarentena. LiteLLM se descarga alrededor de 3.4 millones de veces al día.

Snyk ha estado siguiendo este incidente. Si eres cliente de Snyk, es posible que ya hayas visto la alerta en el banner de la aplicación y recibido una notificación por correo electrónico. El registro de la vulnerabilidad es SNYK-PYTHON-LITELLM-15762713, y las actualizaciones de estado están en Snyk Trust Center.

En resumen

Paquete afectado

litellm (PyPI)

Versiones afectadas

1.82.7, 1.82.8

Versiones seguras

≤ 1.82.6

ID de Snyk

Primera detección

10:39 UTC, 24 de marzo de 2026 (carga de la versión 1.82.7)

Cuarentena de PyPI

\~13:38 UTC, 24 de marzo de 2026

Atacante

TeamPCP (también: PCPcat, Persy_PCP, ShellForce, DeadCatx3)

Vector de ataque

Cadena de suministro: credenciales comprometidas de publicación en PyPI mediante una GitHub Action de Trivy manipulada en CI/CD de LiteLLM

Tipo de carga maliciosa

Tres etapas: recolector de credenciales + exfiltración cifrada + puerta trasera persistente + gusano de Kubernetes

Dominio de exfiltración

models.litellm.cloud (registrado el 23 de marzo de 2026)

MITRE ATT\&CK

T1546.018 (ganchos de inicio de Python), T1003 (volcado de credenciales), T1610 (implementación de contenedores)

Eventos principales

Hora (UTC)

Evidencia

Evento

Finales de febrero de 2026

MegaGame10418 ejecuta un Pwn Request contra la CI de Trivy y explota un flujo de trabajo pull_request_target para exfiltrar las credenciales de aqua-bot

19 de mar., 17:43 UTC

Se reescriben las etiquetas de la GitHub Action de Trivy v0.69.4 para que apunten a una versión maliciosa

23 de mar., 12:58 UTC

Endor Labs (capturó los metadatos de PyPI antes de su eliminación)

Se compromete la GitHub Action de Checkmarx KICS; se registran el dominio C2 checkmarx.zone y models.litellm.cloud

24 de mar., 10:39 UTC

Endor Labs (capturó los metadatos de PyPI antes de su eliminación)

Se publica litellm 1.82.7 malicioso en PyPI

24 de mar., 10:52 UTC

Se publica litellm 1.82.8 malicioso en PyPI (13 minutos después de la versión 1.82.7, con un mecanismo de distribución .pth más avanzado)

24 de mar., 11:48 UTC

FutureSearch (Callum McMahon) abre un problema para informar la vulnerabilidad

24 de mar., 12:36 UTC

Se publica un hilo en HN; alcanza 324 puntos

24 de mar., \~12:44 UTC

Problema #24512 en GitHub (visible en las marcas de tiempo de los comentarios)

Una avalancha de comentarios de bots inunda el problema #24512; el problema se cierra desde la cuenta comprometida del responsable

24 de mar., 13:03 UTC

FutureSearch (actualización con marca de tiempo)

FutureSearch confirma el cierre del problema y el spam de bots

24 de mar., 13:48 UTC

Se abre un problema limpio para dar seguimiento

24 de mar., 15:09 UTC

El responsable de LiteLLM confirma que se rotaron todas las claves de GitHub, Docker y PyPI; las cuentas de responsables se migraron a nuevas identidades

24 de mar., 15:27 UTC

Se eliminan las versiones comprometidas; PyPI quita el paquete de cuarentena

Cómo se descubrió

Callum McMahon, de FutureSearch, estaba probando un plugin MCP de Cursor que incluía litellm como dependencia transitiva. Poco después de que Python se iniciara, su computadora dejó de responder porque se agotó la memoria RAM. McMahon rastreó el problema hasta el paquete litellm recién instalado y encontró litellm_init.pth, un archivo de 34,628 bytes en site-packages/, codificado dos veces en base64.

El agotamiento de la memoria RAM fue un efecto secundario de la carga maliciosa, no una función intencional. El mecanismo .pth se activa cada vez que se inicia el intérprete de Python. Como la carga maliciosa inicia un nuevo subproceso de Python, y ese proceso también activa la ejecución de .pth, se produjo una bomba de bifurcación involuntaria. McMahon publicó sus hallazgos en futuresearch.ai y, en menos de una hora, la información se difundió en r/LocalLLaMA, r/Python y Hacker News.

La cadena de ataque

El ataque a LiteLLM comenzó cinco días antes con Trivy.

19 de marzo: Los atacantes reescribieron etiquetas de Git en el repositorio de la GitHub Action trivy-action para que apuntaran a una versión maliciosa (v0.69.4) que contenía la misma carga para robar credenciales y la misma infraestructura de exfiltración que se usaron en operaciones posteriores. (Para obtener todos los detalles del compromiso de Trivy, consulta la cobertura de Snyk sobre el compromiso de la cadena de suministro de las GitHub Actions de Trivy.)

23 de marzo: Se usó la misma infraestructura en un ataque separado contra Checkmarx KICS (Keep Infrastructure as Code Secure). Durante esta operación, se registró y activó el dominio C2 checkmarx.zone, que suplanta a la empresa de seguridad Checkmarx.

24 de marzo: El pipeline de CI/CD de LiteLLM ejecutó Trivy como parte de su proceso de compilación y lo obtuvo de apt sin fijar una versión. La acción comprometida exfiltró el token PYPI_PUBLISH del entorno del ejecutor de GitHub Actions. Con esas credenciales, los atacantes publicaron litellm 1.82.7 a las 10:39 UTC y 1.82.8 a las 10:52 UTC; ambas versiones contenían cargas maliciosas.

Comentario en Hacker News de un responsable de mantenimiento de LiteLLM sobre un compromiso en la cadena de suministro, que explica la vulnerabilidad de CI/CD, el impacto limitado en proxy docker y el estado de cuarentena en PyPI.

Respuesta inicial del responsable de LiteLLM en Hacker News.

Dos mecanismos de distribución

Las dos versiones maliciosas usaron distintas técnicas de inyección:

1.82.7 (inyección en el código fuente): La carga se codificó en base64 y se insertó directamente en litellm/proxy/proxy_server.py. Se ejecuta cuando cualquier elemento importa litellm.proxy, la ruta de importación estándar para el modo de servidor proxy de LiteLLM.

1.82.8 (.pth archivo): Esta versión agregó litellm_init.pth a site-packages/. El mecanismo .pth se activa cada vez que se inicia el intérprete de Python, sin necesidad de importar nada; esto incluye cuando se inicia Python desde pip, python -c o el servidor de lenguaje de un IDE.

El archivo .pth está declarado correctamente en el archivo RECORD de la rueda, por lo que supera la verificación de hash de pip. El paquete pasa todas las comprobaciones de integridad estándar porque el contenido malicioso se publicó con credenciales legítimas, no se inyectó posteriormente. Snyk Learn ofrece una guía detallada sobre cómo funciona este tipo de ataque en Compromise of a Legitimate Package.

Los responsables de CPython han reconocido el riesgo de seguridad de los ganchos de inicio .pth en el problema #113659, pero todavía no se ha aplicado ningún parche. Esta técnica corresponde a MITRE ATT\&CK T1546.018 (Python Startup Hooks).

La carga maliciosa de tres etapas

Etapa 1: recopilación de información

El script recopila:

  • Datos del sistema: nombre de host, variables de entorno, procesos en ejecución, rutas de red y registros de autenticación recientes de /var/log/auth.log

  • Credenciales: claves privadas SSH, archivos .env, credenciales de Git, claves de API del historial del shell, tokens de webhooks de Slack y Discord, configuraciones de CI/CD (Jenkins, Travis CI, Terraform) y hashes de contraseñas de /etc/shadow

  • Credenciales en la nube: credenciales y configuración de AWS, archivos de cuentas de servicio de GCP y Azure, metadatos de la nube mediante consultas directas a IMDS, AWS Secrets Manager y SSM Parameter Store (el script implementa por completo la firma de IMDSv2)

  • Contenedores y orquestación: config.json de Docker (credenciales de registro), archivos kubeconfig de Kubernetes, tokens de cuentas de servicio, secretos y certificados

  • Criptomonedas: archivos de billetera y frases semilla de Bitcoin, Ethereum, Solana, Cardano y Monero; configuraciones de billeteras de hardware Ledger

Imagen image2

Fragmento del código malicioso decodificado que muestra la iteración estructurada y la recopilación de secretos en varios directorios

Etapa 2: cifrado y exfiltración

Los datos recopilados se cifran antes de transmitirse:

  1. Se genera una clave de sesión AES-256 de 32 bytes mediante openssl rand

  2. Los datos se cifran con AES-256-CBC (derivación de clave PBKDF2)

  3. La clave de sesión se cifra con una clave pública RSA de 4096 bits codificada en el script (relleno OAEP)

  4. Todo se agrupa en tpcp.tar.gz

  5. El paquete se envía mediante POST a https://models.litellm.cloud/ con curl

models.litellm.cloud es distinto del dominio legítimo litellm.ai y se registró el 23 de marzo, un día antes de que se publicaran los paquetes maliciosos.

La clave pública RSA de esta carga es idéntica a la que se encontró en las cargas de Trivy y KICS. Investigadores de Wiz confirmaron esta coincidencia en las tres operaciones, lo que la convierte en el vínculo técnico individual más sólido para atribuir los ataques.

Archivos temporales creados durante la exfiltración: session.key, payload.enc, session.key.enc y tpcp.tar.gz en el directorio temporal del sistema.

Imagen image3

Vista neutralizada del código malicioso. El nombre del archivo tpcp.tar.gz es un indicador constante en las operaciones de Trivy, KICS y LiteLLM.

Etapa 3: persistencia y movimiento lateral

Persistencia local:

  • Escribe un script de Python de puerta trasera en ~/.config/sysmon/sysmon.py (o /root/.config/sysmon/sysmon.py)

  • Crea un servicio de usuario de systemd en ~/.config/systemd/user/sysmon.service con la descripción "System Telemetry Service"

  • Lo registra y habilita: systemctl --user daemon-reload && systemctl --user enable --now sysmon.service

El script de persistencia consulta https://checkmarx.zone/raw cada 5 minutos para obtener una URL, descarga el archivo en /tmp/pglog, le asigna permisos de ejecución y lo ejecuta como proceso en segundo plano. El estado se registra en /tmp/.pg_state. En el momento del análisis, el endpoint devolvía URL de YouTube; el operador puede cambiar a la entrega de cargas en vivo cuando quiera.

Movimiento lateral en Kubernetes: Si el script encuentra un token de cuenta de servicio de Kubernetes en la ruta de montaje estándar, lee todos los secretos de cada espacio de nombres. Luego intenta implementar un pod con privilegios en cada nodo de kube-system mediante alpine:latest. Estos pods montan el sistema de archivos del host e instalan la puerta trasera sysmon en el nodo subyacente.

Los pods maliciosos se llaman node-setup-{node_name} (el nombre del nodo se trunca a 35 caracteres) y contienen un contenedor llamado setup.

Acerca de TeamPCP

TeamPCP (también identificado como PCPcat, Persy_PCP, ShellForce y DeadCatx3, según Wiz Threat Center) ha estado activo desde, al menos, diciembre de 2025. El actor mantiene canales de Telegram en @Persy_PCP y @teampcp, e incluye la cadena "TeamPCP Cloud stealer" en sus cargas. Wiz ha seguido toda la campaña (Wiz Threat Center; blog de Wiz; ramimac.me).

El compromiso de LiteLLM es la fase 09 de una campaña en curso. Todas las operaciones comparten una infraestructura constante: el mismo par de claves RSA, el mismo nombre de paquete tpcp.tar.gz y repositorios de GitHub con el prefijo tpcp-docs que se usan como puntos de entrega temporal para preparar C2. Los tres dominios de esta operación comparten registrador (Spaceship, Inc.) y proveedor de alojamiento (DEMENIN B.V.).

El actor también ha desplegado CanisterWorm, que usa Internet Computer Protocol (ICP) como canal C2. Los registradores de dominios y los proveedores de alojamiento no pueden dar de baja los canisters de ICP. Investigadores de seguridad de Aikido documentan este caso como el primer uso observado de ICP como mecanismo C2 en una campaña de cadena de suministro.

Un componente llamado hackerbot-claw usa un agente de IA (openclaw) para automatizar la selección de objetivos de ataque. Investigadores de Aikido documentaron este caso como uno de los primeros usos operativos de un agente de IA en un ataque a la cadena de suministro.

Supresión de problemas

Cuando los miembros de la comunidad comenzaron a reportar el compromiso en el issue #24512 de GitHub, los atacantes publicaron 88 comentarios de bots desde 73 cuentas únicas en una ventana de 102 segundos (12:44-12:46 UTC). Las cuentas utilizadas eran cuentas de desarrolladores previamente comprometidas, no perfiles creados para este fin. El análisis de Rami McCarthy encontró un 76 % de coincidencia de cuentas con la botnet utilizada durante la divulgación de Trivy.

Con la cuenta de mantenedor comprometida krrishdholakia, los atacantes cerraron el issue #24512 con el estado "not planned" e hicieron commits en repositorios no relacionados con el mensaje "teampcp update."

La comunidad abrió un issue paralelo de seguimiento (#24518) y continuó el debate en Hacker News, donde el hilo alcanzó 324 puntos.

Impacto confirmado

Las versiones afectadas estuvieron en PyPI durante aproximadamente 3 horas. Los siguientes proyectos presentaron PR de seguridad o issues el 24 de marzo para fijar una versión distinta de 1.82.7 y 1.82.8:

Proyecto

Evidencia

DSPy

MLflow

PR #21971 integrada

OpenHands

CrewAI

PR #5040 (desacoplada de litellm); PR #5039

langwatch

strands-agents/sdk-python

Arize Phoenix

nanobot

dreadnode/rigging

CoPaw

Aider

Confirmado como seguro (fija litellm==1.82.3)

El mecanismo .pth se activa cada vez que se inicia un proceso de Python, incluido el propio pip. En entornos de CI/CD, esto significa que la carga maliciosa puede ejecutarse durante los pasos de compilación, no solo al ejecutar la aplicación.

Detección: ¿estás afectado?

Paso 1: Comprueba la versión instalada

pip show litellm | grep Version

Si el resultado muestra 1.82.7 o 1.82.8, considera que el sistema está comprometido y sigue los pasos de corrección que se indican a continuación. No te limites a actualizar: es posible que la carga maliciosa ya se haya ejecutado.

Paso 2: Busca artefactos de persistencia

# Check for the sysmon backdoor
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null && echo "BACKDOOR FOUND"
ls -la /root/.config/sysmon/sysmon.py 2>/dev/null && echo "ROOT BACKDOOR FOUND"

# Check for the systemd persistence service
systemctl --user status sysmon.service 2>/dev/null
ls -la ~/.config/systemd/user/sysmon.service 2>/dev/null && echo "PERSISTENCE SERVICE FOUND"

# Check for exfiltration archive remnants
ls /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc /tmp/session.key.enc 2>/dev/null && echo "EXFIL ARTIFACTS FOUND"

Paso 3: Busca archivos .pth maliciosos

# Find .pth files in site-packages with suspicious patterns
find $(python3 -c "import site; print(' '.join(site.getsitepackages()))") \
  -name "*.pth" -exec grep -l "base64\|subprocess\|exec" {} \;

Paso 4: Verifica los hashes de los archivos

# Check proxy_server.py (1.82.7)
find / -path "*/litellm/proxy/proxy_server.py" 2>/dev/null -exec shasum -a 256 {} \;
# Malicious: a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b

# Check litellm_init.pth (1.82.8)
find / -name "litellm_init.pth" 2>/dev/null -exec shasum -a 256 {} \;
# Malicious: 71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238

Paso 5: Busca indicadores de red

grep "litellm.cloud\|checkmarx.zone" /etc/hosts
grep "models.litellm.cloud\|checkmarx.zone" /var/log/syslog 2>/dev/null

Paso 6: Revisa Kubernetes

kubectl get pods -A | grep "node-setup-"

Paso 7: Analiza con Snyk

snyk test --package-manager=pip
Panel de seguridad de Snyk que muestra métricas de repositorios, como un 61 % analizado, repositorios inactivos y una lista de repositorios de clase A de alto riesgo con vulnerabilidades críticas.

La alerta en el banner de la aplicación de Snyk muestra los repositorios afectados en el inventario de activos. El enlace al Trust Center lleva al estado del incidente en tiempo real.

Los clientes de Snyk también pueden consultar el asesor de paquetes de litellm y el registro completo de la vulnerabilidad en SNYK-PYTHON-LITELLM-15762713.

Corrección

Si NO instalaste 1.82.7 ni 1.82.8:

Fija la versión en <=1.82.6 hasta que haya una versión limpia:

pip install "litellm<=1.82.6"
# requirements.txt:
litellm<=1.82.6

Si SÍ instalaste 1.82.7 o 1.82.8:

La carga maliciosa se ejecuta al iniciar Python, incluso durante el propio pip install. Considera que el sistema podría estar comprometido, independientemente de si ejecutaste código de alguna aplicación.

  1. Elimina los artefactos de persistencia:

rm -f ~/.config/sysmon/sysmon.py
rm -f ~/.config/systemd/user/sysmon.service
systemctl --user disable sysmon.service 2>/dev/null
systemctl --user daemon-reload
rm -f /tmp/tpcp.tar.gz /tmp/session.key /tmp/payload.enc /tmp/session.key.enc /tmp/.pg_state /tmp/pglog
  1. Rota las credenciales del sistema afectado:

  • Claves privadas SSH: genera claves nuevas y revoca las anteriores en authorized_keys, GitHub y GitLab

  • Credenciales de la nube: claves de acceso de AWS, claves de cuentas de servicio de GCP y entidades de servicio de Azure

  • Claves de API: archivos .env, variables de entorno del shell y secretos de CI/CD

  • Credenciales del registro de Docker: ~/.docker/config.json

  • Kubernetes: ~/.kube/config, tokens de cuentas de servicio dentro del clúster

  • Contraseñas de bases de datos en cualquier archivo de configuración del sistema

  • Credenciales de Git de ~/.gitconfig o del almacén de credenciales del sistema

  • Frases semilla de billeteras de criptomonedas

  1. Audita AWS Secrets Manager y SSM Parameter Store, ya que la carga maliciosa consulta estos servicios directamente si puede acceder a los metadatos de la instancia.

  1. Audita los secretos del clúster de Kubernetes. Si había un token de cuenta de servicio, es posible que se hayan leído todos los secretos de todos los espacios de nombres. Busca pods node-setup-* en kube-system.

  1. Instala una versión limpia en un entorno nuevo en lugar de actualizar la instalación existente:

pip install "litellm<=1.82.6"

Por qué la verificación de hashes de pip no detectó el problema

La verificación de hashes confirma que un archivo coincide con lo que PyPI anunció, pero no indica si el contenido anunciado es malicioso.

El archivo litellm_init.pth de la versión 1.82.8 está declarado correctamente en el archivo RECORD del wheel y tiene un hash coincidente. pip install --require-hashes habría finalizado sin errores. El paquete supera todas las comprobaciones de integridad estándar porque el contenido malicioso se publicó con credenciales legítimas: no hay discrepancias de hash, dominios sospechosos ni nombres de paquetes mal escritos.

La única forma de detectar el problema durante la instalación es comprobar si un paquete instala archivos .pth y si estos contienen patrones como subprocess, base64 o exec. Actualmente, ningún complemento de pip de uso generalizado lo hace automáticamente.

El patrón más amplio

La selección de objetivos de esta campaña se centra en herramientas con acceso elevado a canalizaciones automatizadas: un escáner de contenedores (Trivy), una herramienta de análisis de infraestructura (KICS) y una biblioteca de enrutamiento de modelos de IA (LiteLLM). Por diseño, cada una de estas herramientas requiere amplio acceso de lectura a los sistemas con los que opera (credenciales, configuraciones, variables de entorno).

LiteLLM se implementa cada vez más como una puerta de enlace centralizada para LLM que almacena credenciales de API de varios proveedores de modelos. En esa configuración, el conjunto de credenciales al que se puede acceder desde un único host comprometido es más amplio que en una aplicación típica.

La divulgación inicial se difundió a través de comunidades de desarrolladores de IA (r/LocalLLaMA, r/Python, Hacker News), en lugar de los canales de seguridad tradicionales, como r/netsec o los feeds de CVE.

Para leer más sobre los riesgos de la cadena de suministro específicos de las herramientas de LLM, Snyk Learn aborda el tema en Vulnerabilidades de la cadena de suministro en los LLM. El ataque a la cadena de suministro de Ultralytics AI Pwn Request de 2024 también es un caso de comparación útil: otra biblioteca de Python para IA de uso extendido que se vio comprometida mediante un exploit de CI/CD, con una cadena de ataque similar.

Indicadores de compromiso

Hashes de archivos:

Archivo

SHA-256

litellm_init.pth (1.82.8)

71e35aef03099cd1f2d6446734273025a163597de93912df321ef118bf135238

proxy_server.py (1.82.7)

a0d229be8efcb2f9135e2ad55ba275b76ddcfeb55fa4370e0a522a5bdee0120b

sysmon.py

6cf223aea68b0e8031ff68251e30b6017a0513fe152e235c26f248ba1e15c92a

Red:

  • Exfiltración: https://models.litellm.cloud/ (POST)

  • Consultas C2: https://checkmarx.zone/raw (GET)

Sistema de archivos:

  • ~/.config/sysmon/sysmon.py o /root/.config/sysmon/sysmon.py

  • ~/.config/systemd/user/sysmon.service (descripción: "System Telemetry Service")

  • /tmp/tpcp.tar.gz, /tmp/session.key, /tmp/payload.enc, /tmp/session.key.enc

  • /tmp/.pg_state, /tmp/pglog

Kubernetes:

  • Pods: node-setup-{node_name} en kube-system

  • Nombre del contenedor: setup, imagen: alpine:latest

Prefijo de la clave pública RSA (codificado de forma fija en las cargas de las tres operaciones):

MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvahaZDo8mucujrT15ry+...

Qué hacer ahora mismo

  1. Comprueba la versión de litellm: pip show litellm | grep Version

  2. Fija la versión en <=1.82.6 en todos los entornos

  3. Ejecuta snyk test --package-manager=pip

  4. Si tenías instalada la versión 1.82.7 o 1.82.8: rota las credenciales y busca artefactos de persistencia

  5. Audita las canalizaciones de CI/CD para detectar versiones de herramientas sin fijar, incluidas las GitHub Actions

  6. Revisa Kubernetes: kubectl get pods -A | grep node-setup-

DOCUMENTO TÉCNICO

La crisis de seguridad de IA en tu entorno de Python

Con el ritmo de desarrollo acelerándose a un ritmo vertiginoso, ¿sabes realmente a qué puede acceder tu entorno de IA?