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.
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.
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.