Voltar à sala de imprensa
Ameaças

Gheist: tokens do GitHub roubados transformam os logs do Actions num canal de exfiltração de segredos

Uma campanha automatizada usa tokens do GitHub roubados para plantar um workflow de um único ficheiro que imprime todos os segredos de um repositório no seu próprio log do Actions, descarrega o log e apaga a execução. Com mais de 13.500 ramos plantados em mais de 850 contas, a exposição concentra-se nos repositórios privados e no GitHub Enterprise Server, onde a retenção de execuções suspeitas do GitHub não se aplica.

Gheist: tokens do GitHub roubados transformam os logs do Actions num canal de exfiltração de segredos

Gheist: tokens do GitHub roubados transformam os logs do Actions num canal de exfiltração de segredos

Uma campanha totalmente automatizada, seguida sob o nome Gheist, está a usar tokens do GitHub roubados para plantar um workflow de um único ficheiro em cada repositório onde esses tokens têm permissão de escrita. O workflow imprime todos os segredos que o repositório consegue ler no seu próprio log do GitHub Actions, os atacantes descarregam o log através da API do GitHub e a execução é apagada segundos depois. Nenhum servidor do atacante é contactado: os segredos saem através do próprio GitHub.

A operação está em curso pelo menos desde meados de setembro e continuava ativa e em evolução a 24 de outubro. Foram identificados mais de 13.500 ramos plantados em mais de 850 contas comprometidas, desde projetos pessoais até aos repositórios privados de organizações empresariais. Não se sabe quantas dessas organizações perderam efetivamente segredos.

Como funciona o ataque

A ferramenta precisa de exatamente uma coisa: um token do GitHub com permissão de escrita num repositório. Com ele, a sequência é sempre a mesma.

  1. Plantar um ramo. Um ramo novo chamado ci-sync- seguido de seis caracteres aleatórios é enviado para o repositório. Contém um único commit, com a mensagem ci: sync config, que adiciona um só ficheiro: .github/workflows/sync-check.yml. O commit não está assinado e leva o nome do dono do token. O próprio push dispara o workflow.
  2. Despejar os segredos. O workflow imprime o ambiente do job e todos os segredos que o job consegue ler, codificados em base32. O GitHub mascara os valores dos segredos nos logs, mas a máscara não é garantida depois de um valor ter sido codificado ou transformado. A saída em base32 já não corresponde ao segredo guardado, pelo que é escrita por inteiro no log.
  3. Recolher e apagar. A ferramenta descarrega o log do workflow através da API e elimina a execução em poucos segundos.

Dois pormenores tornam esta variante mais difícil de detetar e de conter. O workflow declara um bloco permissions vazio, que restringe o GITHUB_TOKEN do job (de que os atacantes não precisam) mas não o impede de todo de ler segredos. E uma versão da ferramenta executa um job adicional por cada ambiente de deployment configurado no repositório, pelo que os segredos limitados a esses ambientes, frequentemente credenciais de produção, também são impressos.

Apagar o ramo ou a execução não desfaz a exposição. Quando qualquer um deles desaparece, o log já foi descarregado.

A campanha deve o nome à string de user agent gheist, que a ferramenta envia com cada pedido à API do GitHub.

Porque é que o risco real está nos repositórios privados e no Enterprise Server

Desde 28 de julho, o GitHub retém as execuções de workflow que identifica como potencialmente maliciosas até que um colaborador com permissão de escrita as aprove. Todas as execuções Gheist examinadas até agora em repositórios públicos, em todas as versões da ferramenta, continuavam à espera nessa retenção, pelo que o payload nunca chegou a correr.

Essa salvaguarda aplica-se atualmente apenas a repositórios públicos no github.com. Não cobre repositórios privados, e o GitHub Enterprise Server não a tem de todo. Nesses ambientes, o cenário é diferente:

  • Os logs de auditoria do GitHub mostram execuções nos ramos plantados a terminar e a serem depois apagadas pelo user agent gheist.
  • Os registos do AWS CloudTrail mostram os workflows da própria organização também a correr nos ramos ci-sync- e a assumir roles da AWS, o que confirma que o Actions correu ali sem qualquer passo de aprovação.
  • Em organizações empresariais, a ferramenta alternou em minutos entre tokens de vários utilizadores e contas de automação, clonando repositórios privados antes de plantar o workflow. Horas depois, credenciais cloud da mesma organização foram usadas para enumerar o AWS Secrets Manager. De onde vieram essas credenciais, e quem as usou, não foi apurado.

A retenção também só apanha as execuções que as heurísticas do GitHub sinalizam. Uma campanha distinta, a GhostAction, continuou a correr em repositórios públicos até o GitHub começar a bloqueá-la a 9 de outubro. A Gheist é distinta da GhostAction e usa ficheiros de workflow diferentes.

Uma nova variante para runners self-hosted

Por volta de 19 de outubro, uma versão da ferramenta começou a enviar um segundo job dirigido a runners self-hosted, as máquinas que as organizações operam por conta própria para processar jobs do Actions, e parece visar apenas repositórios com um runner desse tipo registado. O job procura no runner credenciais de 31 tipos de tokens: chaves privadas, ambientes de processos, credenciais de role de instância AWS, ficheiros de credenciais do runner e o token da service account do Kubernetes. Para já parece ter erros e não foi observada nenhuma execução bem-sucedida, mas a intenção é clara: pivotar dos segredos de CI para a infraestrutura por trás do runner.

De onde vêm os tokens, e quem mais os usa

Os tokens roubados que foi possível rastrear têm origem em logs de infostealers e em tokens expostos em incidentes anteriores de supply chain, incluindo o comprometimento do scanner de vulnerabilidades Trivy em março. As mesmas contas comprometidas estão a ser partilhadas com outros operadores:

  • A 6 de outubro, 391 contas, muitas delas também usadas pela Gheist, renomearam cerca de 1.461 repositórios para se fazerem passar por software pirateado e distribuir malware. Essa renomeação apagou 610 dos ramos plantados.
  • Algumas vítimas parecem estar também infetadas com o PolinRider, um malware que tem sido associado a operadores norte-coreanos. A 19 e 20 de outubro, três delas voltaram a enviar os seus commits Gheist com um loader adicionado a um ficheiro vite.config.js.
  • Cinquenta e sete das contas já tinham sido atingidas pela Megalodon, uma campanha que em maio introduziu workflows de roubo de segredos em milhares de repositórios do GitHub.

Como verificar os seus repositórios

  • Procure ramos com o padrão ci-sync- mais seis caracteres em que a única alteração seja um ficheiro novo .github/workflows/sync-check.yml com a mensagem de commit ci: sync config.
  • Nos logs de auditoria do GitHub, procure execuções de workflow em ramos ci-sync-* apagadas menos de um minuto depois de terminarem, e qualquer atividade do user agent gheist.
  • Se os seus workflows se autenticam num fornecedor cloud por OIDC, procure nos logs de auditoria da cloud assunções de role ou trocas de token cujo subject inclua um ramo ci-sync-. Isto funciona mesmo que não guarde os logs do GitHub. Note que, por omissão, o subject OIDC inclui o nome do ramo apenas quando o job não usa um ambiente de deployment.
  • Em runners self-hosted, procure os ficheiros .gdmp_pat, .gdmp_patg e .gdmp_sweep, e a string gdmp-pivot nos logs dos jobs.

Indicadores para as suas ferramentas de deteção:

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

As versões do workflow que enumeram os ambientes de deployment de um repositório variam de repositório para repositório e, por isso, não têm um hash fixo.

O que fazer se encontrar um ramo ci-sync

  • Rode todos os segredos de repositório, de organização e de ambiente a que o repositório podia chegar. Assuma que todos foram lidos.
  • Revogue os tokens que enviaram os commits e verifique os dispositivos dos seus donos à procura de infostealers.
  • Se um ramo ci-sync- correu num runner self-hosted, trate essa máquina e a sua role de instância cloud como comprometidas.
  • Não aprove execuções sync-check retidas e não execute código de ramos ci-sync-.

Como tornar um token roubado menos valioso

  • Exija revisores nos ambientes que guardam segredos de produção. Um job não consegue ler os segredos de um ambiente até que um revisor obrigatório o aprove.
  • Restrinja as políticas de confiança OIDC a ramos ou ambientes protegidos, para que um ramo criado por um atacante não possa assumir as suas roles cloud.
  • Prefira GitHub Apps ou tokens granulares de curta duração aos tokens de acesso pessoal de longa duração.
  • Execute os runners self-hosted como máquinas efémeras, sem acesso ao socket do Docker e sem acesso aberto aos metadados da cloud.

A Gheist é um lembrete de que uma plataforma de CI é um cofre de credenciais com um botão de build acoplado. A raiz desta campanha não é uma vulnerabilidade do GitHub, mas um fornecimento constante de tokens de programadores recolhidos por infostealers e expostos em fugas anteriores. Saber quando os tokens da sua organização aparecem nesses logs de stealers e nessas fugas de dados, antes que alguém automatize a sua utilização, é exatamente aquilo que o nosso serviço de monitorização da Deep/Dark Web foi construído para detetar.

PARTILHAR