← Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El token robado no necesitaba ningún servidor

Wiz Research ha rastreado Gheist, una campaña automatizada que toma tokens de GitHub de registros de infostealers y de antiguas filtraciones de la cadena de suministro y planta un workflow que vuelca los secretos en cada repositorio al que llega cada token: más de 13.500 ramas de más de 850 cuentas. Los secretos salen por la propia API de GitHub, así que no hay ningún servidor del atacante que bloquear. En Bromure Agentic Coding, el token de GitHub que un stealer encuentra en la máquina de un desarrollador es un sustituto que GitHub rechaza desde cualquier otro lugar, y cada escritura desde dentro de la máquina espera un cuadro de diálogo en tu Mac.

Gheist no tiene servidor de mando. GitHub ejecuta la carga útil, GitHub escribe los secretos en un registro y GitHub entrega ese registro a quien tenga el token.

El 25 de octubre, Wiz Research publicó su análisis de Gheist, una campaña que sigue desde el 15 de septiembre. Una herramienta automatizada toma un token robado de GitHub, enumera todos los repositorios a los que llega ese token y planta un workflow que imprime los secretos del repositorio. Wiz ha recuperado más de 13.500 ramas plantadas desde más de 850 cuentas comprometidas, y a fecha de 24 de octubre la campaña seguía activa y seguía cambiando. The Hacker News lo publicó ayer.

La herramienta solo necesita una entrada, y Wiz dice de dónde sale: los tokens coinciden con registros de infostealers y con tokens expuestos en compromisos anteriores de la cadena de suministro, como el del escáner Trivy en marzo. Los consejos de Wiz a cada víctima incluyen esta línea: revisa si los dispositivos de los dueños de los tokens tienen infostealers.

Cómo funciona Gheist

Para cada repositorio, la herramienta crea una rama llamada ci-sync- más seis caracteres aleatorios y hace commit de un solo archivo, .github/workflows/sync-check.yml, con el mensaje ci: sync config. El commit no está firmado y se atribuye al dueño del token. Sus peticiones a la API llevan el user agent gheist, que dio nombre a la campaña.

El push arranca el workflow. El job imprime sus variables de entorno y todo el contexto secrets en el registro de la ejecución, ambos codificados en base32. Wiz explica por qué funciona: "GitHub enmascara los valores secretos en los registros solo cuando aparecen tal cual." La salida en base32 no coincide con los valores almacenados, así que no se enmascara nada. Una matriz sobre los entornos de despliegue del repositorio arrastra los secretos de ámbito de entorno, que a menudo son los de producción.

Después, la herramienta descarga el registro mediante la API de GitHub y borra la ejecución segundos más tarde. En palabras de Wiz: "Como los secretos salen por la propia API de GitHub, no hay ningún dominio del atacante ni conexión saliente que bloquear."

La herramienta no dejó de mejorar. Una variante añadió un tiempo límite de seis minutos, la matriz de entornos y cambios de carga útil por repositorio a las pocas horas de su primera ejecución, el 28 de septiembre. El 19 de octubre incorporó un job dirigido a runners autoalojados que busca 31 tipos de token, credenciales de instancias en la nube y el token de la cuenta de servicio de Kubernetes.

De un token robado a los secretos de un repositorio1 · Un token robadode un registro de infostealero una fuga de supply chainmás de 850 cuentas2 · Una rama por repositorioci-sync-xxxxxxañade sync-check.ymlmás de 13.500 ramas3 · Secretos al registrotoJSON(secrets) | base32el enmascarado no vevalores codificados4 · Descargar y borrarregistro bajado por la API,ejecución borrada enseguida;ningún host del atacanteRepositorios públicos · retenidos por GitHubdesde el 28 de julio, las ejecuciones marcadas esperana un colaborador; todas las de Gheist vistas por Wizquedaron en action_requiredRepositorios privados y GHES · se ejecutaronlos registros de auditoría muestran ejecuciones ci-sync-*terminadas y borradas; CloudTrail muestra roles de AWSasumidos desde esas ramas
La cadena de Gheist, tal como la describe Wiz. Un token sacado de un registro de infostealer o de una antigua filtración de la cadena de suministro permite a una herramienta automatizada subir una rama ci-sync- con un único archivo de workflow a cada repositorio al que llega el token. El workflow imprime cada secreto en base32, esquivando el enmascaramiento de registros de GitHub, y la herramienta descarga el registro por la API de GitHub y borra la ejecución. En ningún paso interviene un servidor del atacante.

Dónde se detiene la nueva regla de GitHub

Desde el 28 de julio, GitHub retiene las ejecuciones de workflow que marca como maliciosas en repositorios públicos hasta que un colaborador las aprueba. Todas las ejecuciones públicas de Gheist que revisó Wiz estaban detenidas en action_required. "El control funcionó, y las cargas maliciosas nunca se ejecutaron."

La retención solo cubre los repositorios públicos de github.com. En repositorios privados y en GitHub Enterprise Server, Wiz encontró registros de auditoría con ejecuciones ci-sync-* que terminaron y luego se borraron, y registros de CloudTrail de workflows de organizaciones que asumían roles de AWS desde esas ramas.

El token sale de la máquina de un desarrollador

Cada rama de Gheist necesita un token válido, y Wiz ha rastreado esos tokens hasta registros de stealers y herramientas de compilación filtradas. En el portátil de un desarrollador, el inicio de sesión de la CLI gh, un token de acceso personal en ~/.git-credentials, un GH_TOKEN en un perfil de shell: un infostealer lo copia todo de una pasada. Los agentes de código han hecho que estos tokens sean más habituales, porque un agente que sube ramas y abre pull requests necesita tener uno a mano en todo momento.

El robo puede venir de una herramienta de confianza. El 19 de octubre, un atacante que llevaba meses controlando la cuenta de GitHub de un desarrollador de OVHcloud cambió los binarios de cinco versiones de la CLI de OVHcloud, de la v0.11.0 a la v0.15.0, por compilaciones que, en palabras del CEO de OVHcloud, Octave Klaba, "recuperan secretos del disco". BleepingComputer informó de que las compilaciones para macOS, Linux y Windows estaban todas afectadas. El consejo de Klaba: "Si estás afectado, cambia todos los secretos de tu disco."

Un token robado no tiene por qué llegar el mismo día a una campaña como Gheist. Los registros de stealers se comercian, y un token sigue siendo útil hasta que alguien lo revoca.

El mismo token en una máquina Bromure

Bromure Agentic Coding ejecuta Claude Code, Codex, Grok, Kimi y omp en VMs Ubuntu en tu Mac. Las credenciales reales se quedan en el host, y el agente trabaja con sustitutos. Sigue un token de GitHub por la cadena de Gheist en una de esas máquinas.

El token del disco es un sustituto

Añade tu token de GitHub a la máquina en Credentials. Al arrancar, Bromure escribe uno falso en la VM: ghp_ más 36 caracteres, la forma que espera gh, en GH_TOKEN, en la configuración de gh y en ~/.git-credentials. El git push y el gh pr create del agente funcionan como antes. Cuando una petición sale de la VM hacia github.com, el proxy del Mac cambia el falso por el token real, y solo para ese host.

Ahora ejecuta la CLI troyanizada dentro de esa VM, como haría un agente después de seguir las instrucciones de instalación de un proyecto. Lee el disco, y cada valor de GitHub que hay en él es el sustituto. Lo mismo vale para el resto del botín habitual de un desarrollador. ~/.aws/config apunta a un asistente que entrega una clave secreta falsa, y el proxy vuelve a firmar las peticiones de AWS con la real en el Mac. La VM no tiene claves privadas SSH que leer: SSH_AUTH_SOCK lleva a un agente en el host, que firma por la VM y nunca entrega la clave.

Gheist no puede usar el sustituto

La herramienta de Gheist se ejecuta en la propia infraestructura del atacante. Llama a api.github.com con el token que compró y espera ver repositorios. Con un sustituto de Bromure, GitHub responde 401 Bad credentials. Bromure deriva el sustituto del token real con una sal que se queda en tu Mac, y solo el proxy de ese Mac sabe a qué token real corresponde. La herramienta no puede enumerar ni un solo repositorio, así que nunca crea una rama.

Todo el método de Gheist consiste en preguntar a qué llega un token. Fuera de la VM en la que se escribió, un sustituto de Bromure no llega a nada.

Un sustituto camino de un stealer hace saltar una alarma

El stealer todavía tiene que enviar lo que encontró a alguna parte, y desde una VM de Bromure toda conexión HTTPS pasa por el proxy del Mac. El proxy examina cada petición saliente, cabeceras y cuerpo, en busca de sustitutos que vayan a un host para el que no se crearon. Un sustituto ghp_ con destino a algo que no sea GitHub se bloquea con un HTTP 451 antes de que salga un solo byte del Mac. La VM se pausa y una alerta muestra This environment may have been compromised, con el nombre de la credencial, el host al que pertenece y el host al que se envió.

Puedes apagar la máquina, o guardar su disco y su carpeta personal para investigarlos. Una máquina marcada como comprometida no vuelve a arrancar hasta que se borran su disco y su carpeta personal, así que el stealer no sobrevive al reinicio.

Pon Unmatched traffic en Deny en el panel Guardrails de la máquina, permite los hosts que necesita el proyecto, y el host de subida del stealer se rechaza de entrada, con archivo cifrado o sin él. La conexión rechazada queda como una fila en la Security Timeline.

Las escrituras desde la máquina te esperan

Supongamos que el malware renuncia al robo y usa el sustituto donde funciona, desde dentro de la VM, a través del proxy. Tendría que hacer lo que hace Gheist: crear una rama y hacer commit de un workflow.

Las máquinas nuevas de Bromure ponen la política de escritura de GitHub en Prompt before write. Las lecturas pasan. Un git push o una escritura REST a github.com se detiene en un cuadro de diálogo en tu Mac que muestra la operación tal cual:

Allow write on "GitHub" from workspace "web-app"?
POST /repos/<owner>/<repo>/git/refs

Una rama ci-sync- que no pediste, que aparece mientras mirabas una pull request, es fácil de rechazar. Don't allow devuelve al agente el mismo error que daría una política de bloqueo, y Allow once no concede nada más allá de esa única escritura.

Portátil: un token válido en el discoEl stealer lee el discosesión de gh, ~/.git-credentials, GH_TOKEN: el token realRegistro de stealer, luego la herramienta gheistfunciona desde cualquier máquina de internetapi.github.com lo aceptaramas ci-sync- en cada repositorio al que llega;los secretos salen por la propia API de GitHubBromure: un sustituto en la VMVM del agente (Ubuntu)config de gh, ~/.git-credentials, GH_TOKEN = sustituto ghp_Proxy del host en el Mactoken real puesto solo para github.com;sustituto a otro host: 451, VM en pausa; escritura preguntaDesde la infraestructura del atacanteel sustituto no es un token de GitHub;api.github.com lo rechaza, no se planta nada
Gheist en un portátil y en una máquina Bromure. En el portátil, un stealer copia del disco un token de GitHub válido y la herramienta gheist lo usa desde cualquier lugar. En una máquina Bromure, la VM guarda un sustituto ghp_: GitHub lo rechaza desde la infraestructura del atacante, el proxy lo bloquea camino del host del stealer y pausa la VM, y cualquier escritura desde la VM espera un cuadro de diálogo en el Mac.

La rotación se hace en un solo sitio

El primer paso de respuesta de Wiz es rotar cada secreto al que podía llegar un repositorio afectado y revocar los tokens que subieron los commits. "Borrar la ejecución o la rama no deshace la exposición."

En un Mac con Bromure, la parte de esa rotación que toca al desarrollador es un solo campo. El token real de GitHub vive en una única entrada cifrada en el host. Pega el nuevo en el panel Credentials de la máquina y el siguiente arranque escribe el sustituto correspondiente; no hay copias que perseguir en perfiles de shell, archivos .env ni configuraciones de agentes. Para una clave guardada como referencia de 1Password (op://vault/item/field en Other API keys), Bromure la vuelve a resolver cada dos minutos, así que una rotación en 1Password llega a las máquinas en marcha sin reiniciarlas.

La propia bóveda está protegida por una clave del llavero Data Protection de macOS, ligada a la identidad de firma de Bromure y nunca sincronizada. Un stealer que copie la carpeta de Bromure fuera del Mac solo obtiene texto cifrado.

Deja que un agente haga la búsqueda, en solo lectura

Los pasos de búsqueda de Wiz son un trabajo tedioso sobre muchos repositorios, lo que los convierte en buen trabajo para un agente: enumerar cada rama que coincida con ^ci-sync-[0-9a-z]{6}$, encontrar los commits que solo añaden sync-check.yml con el mensaje ci: sync config, sacar del registro de auditoría las ejecuciones borradas menos de un minuto después de terminar y las del user agent gheist.

Encarga el trabajo a una máquina Bromure cuya política de escritura de GitHub esté en Read-only. El agente puede leer todos los repositorios y registros de auditoría que permita el token, y cualquier push o escritura REST se rechaza en el Mac antes de llegar a GitHub. Si el agente hace checkout de una rama ci-sync- para examinarla, incluida alguna de las ramas que Wiz vio re-subidas con cargadores de PolinRider, todo lo que se ejecute lo hará en una VM llena de sustitutos. Cada cambio de credencial aparece como una fila Credential brokering en la Security Timeline, así que después puedes ver qué hosts tocó la búsqueda.

Los stealers encuentran sustitutos

GH_TOKEN, la configuración de gh y ~/.git-credentials contienen un ghp_ falso. El token real se queda cifrado en el Mac.

Gheist recibe un 401

Fuera de la máquina, el sustituto no es un token de GitHub. La herramienta no puede enumerar repositorios, así que no planta nada.

Las fugas pausan la VM

Un sustituto que va a cualquier sitio que no sea GitHub se bloquea con un 451, y la VM queda en pausa hasta que decidas qué hacer con ella.

Las escrituras preguntan antes

Las máquinas nuevas preguntan antes de cada push y escritura REST a GitHub, con la petición exacta en pantalla. Una búsqueda puede ejecutarse en solo lectura.

Saca el token del portátil

La retención con aprobación de GitHub funcionó en los repositorios públicos, y la lista de refuerzo de Wiz es la correcta: revisores obligatorios en los entornos de producción, confianza OIDC limitada a ramas protegidas, GitHub Apps o tokens granulares de corta duración en lugar de tokens de acceso personal. Todo ello reduce lo que puede hacer un token robado una vez que alguien lo tiene.

Bromure Agentic Coding actúa un paso antes. Tus agentes suben ramas y abren pull requests todo el día, y el token que usan nunca está en un disco que un stealer pueda leer. Gheist solo puede plantar workflows con tokens que todavía funcionan, y el de una máquina Bromure solo funciona a través del proxy de esa misma máquina.

Para ver cómo resisten esos mismos sustitutos a un stealer creado para los archivos de los agentes de código, lee El stealer se llevó los sustitutos. Para un único token robado de GitHub que acabó en una release envenenada, lee El token del agente era el del propietario.