Gheist: tokens de GitHub robados convierten los logs de Actions en un canal de exfiltración de secretos
Una campaña totalmente automatizada, rastreada bajo el nombre Gheist, está usando tokens de GitHub robados para plantar un workflow de un solo archivo en cada repositorio en el que esos tokens tienen permiso de escritura. El workflow imprime todos los secretos que el repositorio puede leer en su propio log de GitHub Actions, los atacantes descargan el log a través de la API de GitHub y la ejecución se elimina segundos después. Nunca se contacta con un servidor del atacante: los secretos salen a través del propio GitHub.
La operación lleva activa al menos desde mediados de septiembre y seguía en marcha y evolucionando a fecha del 24 de octubre. Se han identificado más de 13.500 ramas plantadas en más de 850 cuentas comprometidas, desde proyectos personales hasta los repositorios privados de organizaciones empresariales. No se sabe cuántas de esas organizaciones perdieron realmente secretos.
Cómo funciona el ataque
La herramienta necesita exactamente una cosa: un token de GitHub con permiso de escritura en un repositorio. Con él, la secuencia es siempre la misma.
- Plantar una rama. Se sube al repositorio una rama nueva llamada
ci-sync-seguida de seis caracteres aleatorios. Contiene un único commit, con el mensajeci: sync config, que añade un solo archivo:.github/workflows/sync-check.yml. El commit no está firmado y lleva el nombre del propietario del token. El propio push dispara el workflow. - Volcar los secretos. El workflow imprime el entorno del job y cada secreto que el job puede leer, codificados en base32. GitHub enmascara los valores de los secretos en los logs, pero el enmascarado no está garantizado una vez que un valor se ha codificado o transformado. La salida en base32 ya no coincide con el secreto almacenado, así que se escribe íntegra en el log.
- Recoger y borrar. La herramienta descarga el log del workflow a través de la API y elimina la ejecución en cuestión de segundos.
Dos detalles hacen que esta variante sea más difícil de detectar y de contener. El workflow declara un bloque permissions vacío, que restringe el GITHUB_TOKEN del job (que los atacantes no necesitan) pero no impide en absoluto que lea secretos. Y una versión de la herramienta ejecuta un job adicional por cada entorno de despliegue configurado en el repositorio, de modo que los secretos limitados a esos entornos, con frecuencia credenciales de producción, también se imprimen.
Borrar la rama o la ejecución no deshace la exposición. Para cuando desaparece cualquiera de las dos, el log ya se ha descargado.
La campaña toma su nombre de la cadena de user agent gheist, que la herramienta envía con cada petición a la API de GitHub.
Por qué el riesgo real está en los repositorios privados y en Enterprise Server
Desde el 28 de julio, GitHub retiene las ejecuciones de workflow que identifica como potencialmente maliciosas hasta que un colaborador con permiso de escritura las aprueba. Todas las ejecuciones de Gheist examinadas hasta ahora en repositorios públicos, con cualquier versión de la herramienta, seguían esperando en esa retención, por lo que el payload nunca llegó a ejecutarse.
Esa salvaguarda se aplica actualmente solo a repositorios públicos en github.com. No cubre los repositorios privados, y GitHub Enterprise Server no la tiene en absoluto. En esos entornos el panorama es distinto:
- Los logs de auditoría de GitHub muestran ejecuciones en las ramas plantadas que terminan y luego son eliminadas por el user agent
gheist. - Los registros de AWS CloudTrail muestran que los workflows propios de la organización también se ejecutaron en las ramas
ci-sync-y asumieron roles de AWS, lo que confirma que Actions corrió allí sin ningún paso de aprobación. - En organizaciones empresariales, la herramienta rotó en cuestión de minutos entre tokens de varios usuarios y cuentas de automatización, clonando repositorios privados antes de plantar el workflow. Horas después, credenciales cloud de la misma organización se usaron para enumerar AWS Secrets Manager. De dónde salieron esas credenciales y quién las usó no se ha determinado.
Además, la retención solo atrapa las ejecuciones que las heurísticas de GitHub marcan. Una campaña distinta, GhostAction, siguió funcionando en repositorios públicos hasta que GitHub empezó a bloquearla el 9 de octubre. Gheist es independiente de GhostAction y usa archivos de workflow diferentes.
Una nueva variante para runners autoalojados
Alrededor del 19 de octubre, una versión de la herramienta empezó a subir un segundo job dirigido a runners autoalojados, las máquinas que las organizaciones operan por su cuenta para procesar jobs de Actions, y parece apuntar solo a repositorios que tienen un runner de este tipo registrado. El job busca en el runner credenciales de 31 tipos de tokens: claves privadas, entornos de procesos, credenciales de rol de instancia de AWS, archivos de credenciales del runner y el token de la cuenta de servicio de Kubernetes. Por ahora parece tener errores y no se ha observado ninguna ejecución exitosa, pero la intención es clara: pivotar de los secretos de CI a la infraestructura que hay detrás del runner.
De dónde salen los tokens y quién más los usa
Los tokens robados que se han podido rastrear proceden de logs de infostealers y de tokens expuestos en incidentes anteriores de cadena de suministro, incluido el compromiso del escáner de vulnerabilidades Trivy en marzo. Las mismas cuentas comprometidas se comparten con otros operadores:
- El 6 de octubre, 391 cuentas, muchas de ellas usadas también por Gheist, renombraron unos 1.461 repositorios para hacerse pasar por software pirateado y distribuir malware. Ese renombrado borró 610 de las ramas plantadas.
- Algunas víctimas parecen estar infectadas además con PolinRider, un malware que se ha asociado con operadores norcoreanos. El 19 y el 20 de octubre, tres de ellas volvieron a subir sus commits de Gheist con un loader añadido a un archivo
vite.config.js. - Cincuenta y siete de las cuentas ya habían sido golpeadas por Megalodon, una campaña que en mayo introdujo workflows de robo de secretos en miles de repositorios de GitHub.
Cómo revisar tus repositorios
- Busca ramas con el patrón
ci-sync-más seis caracteres en las que el único cambio sea un archivo nuevo.github/workflows/sync-check.ymlcon el mensaje de commitci: sync config. - En los logs de auditoría de GitHub, busca ejecuciones de workflow en ramas
ci-sync-*eliminadas menos de un minuto después de terminar, y cualquier actividad del user agentgheist. - Si tus workflows se autentican ante un proveedor cloud mediante OIDC, busca en los logs de auditoría del cloud asunciones de rol o intercambios de tokens cuyo subject incluya una rama
ci-sync-. Esto funciona aunque no conserves los logs de GitHub. Ten en cuenta que, por defecto, el subject de OIDC incluye el nombre de la rama solo cuando el job no usa un entorno de despliegue. - En runners autoalojados, busca los archivos
.gdmp_pat,.gdmp_patgy.gdmp_sweep, y la cadenagdmp-pivoten los logs de los jobs.
Indicadores para tus herramientas de detección:
User agent: gheist
Branch: ^ci-sync-[0-9a-z]{6}$
Commit message: ci: sync config (adds only .github/workflows/sync-check.yml)
Log string: toJSON(secrets) }}' | base32 -w 120
Log string: ===== gdmp-pivot:
Files (runner): .gdmp_pat .gdmp_patg .gdmp_sweep
Blob SHA (plain): f3da2b8b357dcda02fd455d314be2a95007516a6
Blob SHA (timeout): d09535857a741bbb976bdc084a9a021713133291
Blob SHA (separator): 1dc5af9cce950aad1eb1546ecce8565b117540c4
Blob SHA (self-hosted job): c665b778b2d822453293003e7ca0bbb2cb08e39d
Las versiones del workflow que enumeran los entornos de despliegue de un repositorio varían de un repositorio a otro y, por tanto, no tienen un hash fijo.
Qué hacer si encuentras una rama ci-sync
- Rota todos los secretos de repositorio, de organización y de entorno a los que el repositorio podía llegar. Asume que todos fueron leídos.
- Revoca los tokens que subieron los commits y revisa los dispositivos de sus propietarios en busca de infostealers.
- Si una rama
ci-sync-se ejecutó en un runner autoalojado, trata esa máquina y su rol de instancia cloud como comprometidos. - No apruebes ejecuciones
sync-checkretenidas y no ejecutes código de ramasci-sync-.
Cómo hacer que un token robado valga menos
- Exige revisores en los entornos que guardan secretos de producción. Un job no puede leer los secretos de un entorno hasta que un revisor obligatorio lo aprueba.
- Limita las políticas de confianza OIDC a ramas o entornos protegidos, para que una rama creada por un atacante no pueda asumir tus roles cloud.
- Prefiere GitHub Apps o tokens granulares de corta duración frente a los tokens de acceso personal de larga vida.
- Ejecuta los runners autoalojados como máquinas efímeras, sin acceso al socket de Docker y sin acceso abierto a los metadatos del cloud.
Gheist recuerda que una plataforma de CI es un almacén de credenciales con un botón de build acoplado. La raíz de esta campaña no es una vulnerabilidad de GitHub, sino un suministro constante de tokens de desarrolladores recolectados por infostealers y filtrados en brechas anteriores. Saber cuándo los tokens de tu organización aparecen en esos logs de stealers y en esos volcados filtrados, antes de que alguien automatice su uso, es exactamente lo que nuestro servicio de monitorización de la Deep/Dark Web está diseñado para detectar.
