← Voltar para todas as publicações
Publicado em · por Renaud Deraison

O token roubado não precisava de servidor

A Wiz Research rastreou o Gheist, uma campanha automatizada que pega tokens do GitHub em logs de infostealers e em vazamentos antigos da cadeia de suprimentos e planta um workflow que despeja segredos em cada repositório que cada token alcança: mais de 13.500 branches de mais de 850 contas. Os segredos saem pela própria API do GitHub, então não há servidor do atacante para bloquear. No Bromure Agentic Coding, o token do GitHub que um stealer encontra na máquina de um desenvolvedor é um substituto que o GitHub rejeita em qualquer outro lugar, e toda escrita feita de dentro da máquina espera por uma caixa de diálogo no seu Mac.

O Gheist não tem servidor de comando. O GitHub executa o payload, o GitHub grava os segredos em um log, e o GitHub entrega o log a quem tiver o token.

Em 25 de outubro, a Wiz Research publicou sua análise do Gheist, uma campanha que ela acompanha desde 15 de setembro. Uma ferramenta automatizada pega um token roubado do GitHub, lista todos os repositórios que esse token alcança e planta um workflow que imprime os segredos do repositório. A Wiz recuperou mais de 13.500 branches plantadas a partir de mais de 850 contas comprometidas e, em 24 de outubro, a campanha ainda estava ativa e ainda mudando. O The Hacker News noticiou o caso ontem.

A ferramenta só precisa de uma entrada, e a Wiz diz de onde ela vem: os tokens correspondem a logs de infostealers e a tokens expostos em comprometimentos anteriores da cadeia de suprimentos, como o do scanner Trivy em março. A orientação da Wiz a cada vítima inclui esta linha: verifique se há infostealers nos dispositivos dos donos dos tokens.

Como o Gheist funciona

Para cada repositório, a ferramenta cria uma branch chamada ci-sync- seguida de seis caracteres aleatórios e faz commit de um único arquivo, .github/workflows/sync-check.yml, com a mensagem ci: sync config. O commit não é assinado e aparece em nome do dono do token. Suas requisições de API usam o user agent gheist, que deu nome à campanha.

O push dispara o workflow. O job imprime suas variáveis de ambiente e todo o contexto secrets no log da execução, ambos codificados em base32. A Wiz explica por que isso funciona: "O GitHub mascara valores secretos nos logs apenas quando eles aparecem literalmente." A saída em base32 não corresponde aos valores armazenados, então nada é mascarado. Uma matriz sobre os ambientes de implantação do repositório puxa os segredos de escopo de ambiente, que muitas vezes são os de produção.

Depois, a ferramenta baixa o log pela API do GitHub e apaga a execução segundos depois. Nas palavras da Wiz: "Como os segredos saem pela própria API do GitHub, não há domínio do atacante nem conexão de saída para bloquear."

A ferramenta continuou melhorando. Uma variante adicionou um timeout de seis minutos, a matriz de ambientes e mudanças de payload por repositório poucas horas depois da primeira execução, em 28 de setembro. Em 19 de outubro, ela ganhou um job voltado a runners auto-hospedados que caça 31 tipos de token, credenciais de instâncias de nuvem e o token da conta de serviço do Kubernetes.

De um token roubado aos segredos de um repositório1 · Um token roubadode um log de infostealerou vazamento de supply chainmais de 850 contas2 · Branch por repositórioci-sync-xxxxxxadiciona sync-check.ymlmais de 13.500 branches3 · Segredos no logtoJSON(secrets) | base32o mascaramento não pegavalores codificados4 · Baixar e apagarlog baixado pela API GitHub,execução apagada em seguida;nenhum host do atacanteRepositórios públicos · retidos pelo GitHubdesde 28 de julho, execuções suspeitas esperam umcolaborador; todas as do Gheist vistas pela Wizficaram em action_requiredRepositórios privados e GHES · rodaramlogs de auditoria mostram execuções ci-sync-* concluídase depois apagadas; o CloudTrail mostra papéis da AWSassumidos a partir dessas branches
A cadeia do Gheist, como a Wiz a descreve. Um token tirado de um log de infostealer ou de um vazamento antigo da cadeia de suprimentos permite que uma ferramenta automatizada envie uma branch ci-sync- com um único arquivo de workflow para cada repositório que o token alcança. O workflow imprime cada segredo em base32, driblando o mascaramento de logs do GitHub, e a ferramenta baixa o log pela API do GitHub e apaga a execução. Nenhum servidor do atacante participa de nenhuma etapa.

Onde a nova regra do GitHub para

Desde 28 de julho, o GitHub retém execuções de workflow que classifica como maliciosas em repositórios públicos até que um colaborador as aprove. Todas as execuções públicas do Gheist que a Wiz verificou estavam paradas em action_required. "O controle funcionou, e os payloads maliciosos nunca rodaram."

A retenção só vale para repositórios públicos no github.com. Em repositórios privados e no GitHub Enterprise Server, a Wiz encontrou logs de auditoria mostrando execuções ci-sync-* que terminaram e depois foram apagadas, além de registros do CloudTrail de workflows de organizações assumindo papéis da AWS a partir dessas branches.

O token vem da máquina de um desenvolvedor

Cada branch do Gheist precisa de um token válido, e a Wiz rastreou esses tokens até logs de stealers e ferramentas de build vazadas. No notebook de um desenvolvedor, o login da CLI gh, um token de acesso pessoal em ~/.git-credentials, um GH_TOKEN em um perfil de shell: um infostealer copia tudo isso de uma vez. Os agentes de código tornaram esses tokens mais comuns, porque um agente que envia branches e abre pull requests precisa ter um ao alcance o tempo todo.

O roubo pode vir de uma ferramenta em que você confia. Em 19 de outubro, um atacante que controlava havia meses a conta do GitHub de um desenvolvedor da OVHcloud trocou os binários de cinco versões da CLI da OVHcloud, da v0.11.0 à v0.15.0, por builds que, nas palavras do CEO da OVHcloud, Octave Klaba, "recuperam segredos do disco". O BleepingComputer informou que as builds para macOS, Linux e Windows foram todas afetadas. O conselho de Klaba: "Se você foi afetado, troque todos os segredos que estão no seu disco."

Um token roubado não precisa chegar a uma campanha como o Gheist no mesmo dia. Logs de stealers são negociados, e um token continua útil até que alguém o revogue.

O mesmo token em uma máquina Bromure

O Bromure Agentic Coding roda Claude Code, Codex, Grok, Kimi e omp em VMs Ubuntu no seu Mac. As credenciais reais ficam no host, e o agente trabalha com substitutos. Siga um token do GitHub pela cadeia do Gheist em uma dessas máquinas.

O token no disco é um substituto

Adicione seu token do GitHub à máquina em Credentials. Na inicialização, o Bromure grava um falso na VM: ghp_ seguido de 36 caracteres, o formato que o gh espera, em GH_TOKEN, na configuração do gh e em ~/.git-credentials. O git push e o gh pr create do agente funcionam como antes. Quando uma requisição sai da VM para o github.com, o proxy no Mac troca o falso pelo token real, e só para esse host.

Agora execute a CLI adulterada dentro dessa VM, como um agente faria depois de seguir as instruções de instalação de um projeto. Ela lê o disco, e todo valor do GitHub que encontra é o substituto. O mesmo vale para o resto do butim habitual de um desenvolvedor. O ~/.aws/config aponta para um auxiliar que entrega uma chave secreta falsa, e o proxy assina de novo as requisições da AWS com a verdadeira, no Mac. A VM não tem chaves privadas SSH para ler: o SSH_AUTH_SOCK leva a um agente no host, que assina pela VM e nunca entrega a chave.

O Gheist não consegue usar o substituto

A ferramenta do Gheist roda na infraestrutura do próprio atacante. Ela chama o api.github.com com o token que comprou e espera ver repositórios. Com um substituto do Bromure, o GitHub responde 401 Bad credentials. O Bromure deriva o substituto do token real com um salt que fica no seu Mac, e só o proxy desse Mac sabe qual token real ele representa. A ferramenta não consegue listar nem um repositório, então nunca cria uma branch.

Todo o método do Gheist consiste em perguntar o que um token alcança. Fora da VM em que foi gravado, um substituto do Bromure não alcança nada.

Um substituto a caminho de um stealer dispara um alarme

O stealer ainda precisa enviar o que encontrou para algum lugar, e, a partir de uma VM do Bromure, toda conexão HTTPS passa pelo proxy no Mac. O proxy examina cada requisição de saída, cabeçalhos e corpo, em busca de substitutos indo para um host para o qual não foram criados. Um substituto ghp_ com destino a qualquer coisa que não seja o GitHub é bloqueado com HTTP 451 antes que um byte saia do Mac. A VM é pausada, e um alerta exibe This environment may have been compromised, com o nome da credencial, o host a que ela pertence e o host para onde foi enviada.

Você pode desligar a máquina ou guardar o disco e a pasta pessoal dela para investigação. Uma máquina marcada como comprometida não inicia de novo até que o disco e a pasta pessoal sejam apagados, então o stealer não sobrevive à reinicialização.

Defina Unmatched traffic como Deny no painel Guardrails da máquina e libere os hosts de que o projeto precisa, e o host de upload do stealer é recusado de imediato, com arquivo criptografado ou não. A conexão recusada vira uma linha na Security Timeline.

As escritas de dentro da máquina esperam por você

Suponha que o malware desista do roubo e use o substituto onde ele funciona, de dentro da VM, através do proxy. Ele teria de fazer o que o Gheist faz: criar uma branch e fazer commit de um workflow.

As novas máquinas Bromure definem a política de escrita do GitHub como Prompt before write. As leituras passam. Um git push ou uma escrita REST no github.com para em uma caixa de diálogo no seu Mac, que mostra a operação literalmente:

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

Uma branch ci-sync- que você não pediu, surgindo enquanto você olhava um pull request, é fácil de recusar. Don't allow devolve ao agente o mesmo erro que uma política de bloqueio daria, e Allow once não concede nada além daquela única escrita.

Notebook: um token válido no discoO stealer lê o discologin do gh, ~/.git-credentials, GH_TOKEN: o token realLog de stealer, depois a ferramenta gheistfunciona de qualquer máquina na interneto api.github.com aceitabranches ci-sync- em cada repositório alcançado;os segredos saem pela própria API do GitHubBromure: um substituto na VMVM do agente (Ubuntu)config gh, ~/.git-credentials, GH_TOKEN = substituto ghp_Proxy do host no Mactoken real inserido só para o github.com;substituto em outro host: 451, VM pausada; escrita perguntaDa infraestrutura do atacanteo substituto não é um token do GitHub;o api.github.com o rejeita, nada é plantado
O Gheist em um notebook e em uma máquina Bromure. No notebook, um stealer copia do disco um token válido do GitHub, e a ferramenta gheist o usa de qualquer lugar. Na máquina Bromure, a VM guarda um substituto ghp_: o GitHub o rejeita vindo da infraestrutura do atacante, o proxy o bloqueia a caminho do host do stealer e pausa a VM, e qualquer escrita de dentro da VM espera por uma caixa de diálogo no Mac.

A rotação acontece em um só lugar

O primeiro passo de resposta da Wiz é trocar todo segredo que um repositório afetado podia alcançar e revogar os tokens que enviaram os commits. "Apagar a execução ou a branch não desfaz a exposição."

Em um Mac com Bromure, o lado do desenvolvedor dessa rotação é um campo só. O token real do GitHub fica em uma única entrada criptografada no host. Cole o novo no painel Credentials da máquina e a próxima inicialização grava o substituto correspondente; não há cópias para caçar em perfis de shell, arquivos .env ou configurações de agentes. Para uma chave guardada como referência do 1Password (op://vault/item/field em Other API keys), o Bromure a resolve de novo a cada dois minutos, então uma rotação no 1Password chega às máquinas em execução sem reinicialização.

O próprio cofre fica protegido por uma chave no Data Protection Keychain do macOS, vinculada à identidade de assinatura do Bromure e nunca sincronizada. Um stealer que copia a pasta do Bromure para fora do Mac leva só texto cifrado.

Deixe um agente fazer a caça, só leitura

Os passos de caça da Wiz são um trabalho tedioso em muitos repositórios, o que os torna um bom trabalho para um agente: listar toda branch que corresponda a ^ci-sync-[0-9a-z]{6}$, encontrar os commits que adicionam apenas sync-check.yml com a mensagem ci: sync config, puxar do log de auditoria as execuções apagadas menos de um minuto depois de terminar e as que usam o user agent gheist.

Entregue a tarefa a uma máquina Bromure cuja política de escrita do GitHub esteja em Read-only. O agente pode ler todo repositório e log de auditoria que o token permite, e qualquer push ou escrita REST é recusado no Mac antes de chegar ao GitHub. Se o agente fizer checkout de uma branch ci-sync- para examiná-la, inclusive uma das branches que a Wiz viu reenviadas com loaders do PolinRider, o que rodar roda em uma VM cheia de substitutos. Cada troca de credencial aparece como uma linha Credential brokering na Security Timeline, então dá para ver depois quais hosts a caça tocou.

Os stealers encontram substitutos

GH_TOKEN, a configuração do gh e ~/.git-credentials guardam um ghp_ falso. O token real fica criptografado no Mac.

O Gheist recebe um 401

Fora da máquina, o substituto não é um token do GitHub. A ferramenta não consegue listar repositórios, então não planta nada.

Vazamentos pausam a VM

Um substituto indo para qualquer lugar que não seja o GitHub é bloqueado com um 451, e a VM fica pausada até você decidir o que fazer com ela.

As escritas perguntam antes

As novas máquinas perguntam antes de cada push e escrita REST no GitHub, com a requisição exata na tela. Uma caça pode rodar só com leitura.

Tire o token do notebook

A retenção com aprovação do GitHub funcionou nos repositórios públicos, e a lista de reforço da Wiz é a certa: revisores obrigatórios nos ambientes de produção, confiança OIDC restrita a branches protegidas, GitHub Apps ou tokens granulares de curta duração em vez de tokens de acesso pessoal. Tudo isso reduz o que um token roubado consegue fazer quando alguém o tem em mãos.

O Bromure Agentic Coding age um passo antes. Seus agentes enviam branches e abrem pull requests o dia todo, e o token que eles usam nunca fica em um disco que um stealer possa ler. O Gheist só consegue plantar workflows com tokens que ainda funcionam, e o de uma máquina Bromure só funciona pelo proxy daquela mesma máquina.

Para ver como esses mesmos substitutos resistem a um stealer feito para os arquivos de agentes de código, leia O stealer levou os substitutos. Para um único token roubado do GitHub que terminou em uma release envenenada, leia O token do agente era o do proprietário.