Uma campanha automatizada está usando tokens do GitHub roubados para inserir workflows (fluxos de automação) que imprimem os segredos de um repositório nos logs do GitHub Actions, que os invasores então baixam antes de apagar as execuções. Os ataques tiveram sucesso em repositórios privados e em ambientes do GitHub Enterprise, segundo uma análise da empresa de segurança em nuvem Wiz.
Uma salvaguarda do GitHub que pausa execuções suspeitas de workflows interrompeu todas as execuções em repositórios públicos verificadas pela Wiz, mas não cobre repositórios privados nem o GitHub Enterprise Server.
Cada workflow fica em uma nova branch chamada ci-sync- seguido de seis caracteres aleatórios. Excluir a branch, ou a execução que ela iniciou, não desfaz a exposição. Todos os segredos que o repositório podia ler devem ser rotacionados (substituídos), e o token que enviou a branch deve ser revogado.
A Wiz acompanha a campanha desde 15 de setembro e a chama de Gheist, em referência ao user agent gheist (identificador do cliente) que a ferramenta dos invasores envia nas requisições à API do GitHub (interface de programação de aplicações).
A Wiz recuperou mais de 13.500 branches inseridas pela campanha a partir de mais de 850 contas comprometidas, de projetos pessoais a repositórios privados de organizações corporativas. A análise da Wiz não informa quantas organizações tiveram segredos expostos.
Em repositórios privados e no GitHub Enterprise Server, os registros de auditoria do GitHub mostram execuções nas branches inseridas sendo concluídas e depois excluídas pelo user agent gheist. Os registros do AWS (Amazon Web Services) CloudTrail mostram que os workflows da própria organização também foram executados nessas branches e assumiram papéis da AWS, o que indica que o GitHub Actions rodou ali sem aprovação.
Em organizações corporativas, a ferramenta alternou tokens de vários usuários e contas de automação em poucos minutos, clonou repositórios privados e só então inseriu o workflow. Horas depois, credenciais de nuvem da mesma organização foram usadas para enumerar o AWS Secrets Manager. A análise da Wiz não informa de onde vieram essas credenciais nem quem as usou.
Até 24 de outubro, a campanha seguia ativa e em transformação. Cerca de dois terços das contas envolvidas já não estavam disponíveis no GitHub. A Wiz comunicou a atividade ao GitHub e afirma que o desaparecimento dessas contas provavelmente reflete uma resposta da plataforma.
Qualquer pessoa com acesso de escrita a um repositório do GitHub pode ler todos os segredos configurados nele, segundo a documentação do GitHub. Um token roubado com esse acesso é suficiente.
Para cada repositório que um token consegue alcançar, a ferramenta cria uma branch ci-sync- e faz o commit de um único arquivo, .github/workflows/sync-check.yml, com a mensagem “ci: sync config”. O commit não é assinado e inclui o nome do dono do token. Ao ser enviado, o workflow é iniciado.
O workflow imprime o ambiente do job e todos os segredos que o job pode ler, codificados em base32. O GitHub mascara segredos nos logs, mas sua documentação afirma que isso não é garantido depois que um valor é codificado ou alterado de outra forma. A Wiz diz que o texto em base32 não corresponde mais ao segredo original, por isso ele é impresso integralmente.
O workflow define um bloco de permissões vazio. Isso limita o GITHUB_TOKEN do job, que os invasores não precisam, mas não impede o job de ler segredos.
Uma versão da ferramenta também executa um job para cada ambiente de implantação do repositório, de modo que segredos restritos a esses ambientes também são impressos. Segundo a Wiz, costumam ser credenciais de produção.
A ferramenta então baixa o log pela API do GitHub e exclui a execução segundos depois. Como os segredos saem pela própria plataforma do GitHub, não há servidor do invasor nem conexão de saída a bloquear.
Desde 28 de julho, o GitHub retém execuções de workflows que identifica como potencialmente maliciosas até que um colaborador com acesso de escrita as aprove. O GitHub afirma que a retenção “atualmente se aplica apenas a repositórios públicos no github.com” e que o GitHub Enterprise Server ainda não a possui.
Todas as execuções públicas do Gheist verificadas pela Wiz, em todas as versões da ferramenta, ainda aguardavam aprovação, então a carga maliciosa nunca foi executada.
A retenção só captura execuções que o GitHub sinaliza. Uma nova variante do GhostAction, campanha separada, rodou em repositórios públicos até o GitHub começar a bloqueá-la em 9 de outubro, segundo pesquisa da empresa de segurança GitGuardian. A Wiz afirma que o Gheist é distinto do GhostAction, que usa arquivos de workflow diferentes.
Por volta de 19 de outubro, uma versão da ferramenta passou a enviar um segundo job destinado a runners auto-hospedados, as máquinas que as organizações mantêm por conta própria para processar jobs do GitHub Actions. Aparentemente, esse job tem como alvo apenas repositórios com um runner desse tipo registrado.
O job foi escrito para vasculhar a máquina do runner em busca de credenciais, incluindo 31 tipos de tokens: chaves privadas, ambientes de processos, credenciais de papel de instância da AWS, arquivos de credenciais do runner e o token da conta de serviço do Kubernetes. A Wiz descreve o job como defeituoso, e sua análise não registra nenhuma execução bem-sucedida.
As fontes que a Wiz conseguiu rastrear para os tokens roubados incluem logs de infostealers (programas de roubo de informações) e tokens expostos em ataques anteriores à cadeia de suprimentos, como o comprometimento do scanner de vulnerabilidades Trivy em março.
Outros invasores também usam as mesmas contas. Em 6 de outubro, 391 contas, muitas delas também usadas pelo Gheist, renomearam cerca de 1.461 repositórios para parecerem softwares pirateados e espalhar malware. A renomeação apagou 610 das branches inseridas.
Algumas vítimas também parecem estar infectadas pelo PolinRider, malware que pesquisadores associaram à Coreia do Norte. Em 19 e 20 de outubro, três delas reenviaram seus commits do Gheist com um carregador (loader) adicionado a um arquivo vite.config.js.
O Megalodon, campanha que inseriu workflows de roubo de segredos em milhares de repositórios do GitHub em maio, também havia atingido 57 das contas.
Como verificar seus repositórios
A Wiz recomenda estas verificações:
- Procure branches com nomes ci-sync- seguidos de seis caracteres, nas quais a única alteração seja um novo arquivo .github/workflows/sync-check.yml com a mensagem de commit “ci: sync config”.
- Nos registros de auditoria do GitHub, procure execuções de workflows em branches ci-sync-* que foram excluídas em menos de um minuto após terminar, e qualquer atividade do user agent gheist.
- Se seus workflows fazem login em um provedor de nuvem com OIDC (OpenID Connect), verifique os registros de auditoria da nuvem em busca de assunções de papéis ou trocas de tokens cujo campo subject (sujeito) inclua uma branch ci-sync-. Isso funciona mesmo que você não colete registros do GitHub.
- Nos runners auto-hospedados, procure os arquivos .gdmp_pat, .gdmp_patg e .gdmp_sweep, além da string gdmp-pivot nos logs dos jobs.
O OIDC permite que workflows façam login com tokens de curta duração, em vez de chaves de nuvem armazenadas. Por padrão, o subject de um token inclui o nome da branch somente quando o job não usa um ambiente de implantação, segundo a documentação do GitHub sobre OIDC.
Os indicadores publicados são:
- User agent: gheist
- Branch: ^ci-sync-[0-9a-z]{6}$
- Mensagem de commit: ci: sync config, adicionando apenas .github/workflows/sync-check.yml
- String: toJSON(secrets) }}' | base32 -w 120
- String: ===== gdmp-pivot:
- Arquivo: .gdmp_pat
- Arquivo: .gdmp_patg
- Arquivo: .gdmp_sweep
- Git blob SHA (SHA, código de hash que identifica o conteúdo de um arquivo), versão simples do workflow: f3da2b8b357dcda02fd455d314be2a95007516a6
- Git blob SHA, versão com timeout: d09535857a741bbb976bdc084a9a021713133291
- Git blob SHA, versão com separador: 1dc5af9cce950aad1eb1546ecce8565b117540c4
- Git blob SHA, versão com job para runners auto-hospedados: c665b778b2d822453293003e7ca0bbb2cb08e39d
As versões do workflow que listam os ambientes de implantação de um repositório variam de um repositório para outro, por isso não têm um hash fixo.
O que fazer se encontrar uma branch ci-sync
A Wiz orienta estes passos para um repositório afetado:
- Rotacione todos os segredos de repositório, de organização e de ambiente que o repositório poderia usar.
- Revogue os tokens que enviaram os commits e verifique os dispositivos de seus donos em busca de infostealers.
- Se uma branch ci-sync foi executada em um runner auto-hospedado, trate a máquina e o papel de instância de nuvem associado a ela como comprometidos.
- Não aprove execuções retidas de sync-check e não execute código de branches ci-sync.
Para reduzir a utilidade de um token roubado, a Wiz também recomenda estes passos:
- Exija revisores em ambientes que guardam segredos de produção.
- Limite as políticas de confiança do OIDC a branches ou ambientes protegidos.
- Use GitHub Apps ou tokens de granularidade fina e curta duração, em vez de tokens de acesso pessoal.
- Execute runners auto-hospedados como máquinas de curta duração, sem acesso ao socket do Docker e sem acesso aberto aos metadados da nuvem.
Um job não pode ler os segredos de um ambiente até que um revisor obrigatório o aprove, segundo a documentação do GitHub.