Segurança de Software

GitHub 'Verified' commits podem ser reescritos em novos hashes sem quebrar assinaturas

Nova pesquisa demonstra que é possível criar um segundo commit com os mesmos arquivos, autor e data, além de uma assinatura válida, enquanto o GitHub continua marcando como 'Verificado'.


Swati Khandelwal Quinta - 09 de Julho de 2026 às 11:20
The Hacker News

Nova pesquisa demonstra que o hash de um commit assinado no Git não é o identificador único que grande parte da indústria de software assume ser. A partir de qualquer commit assinado, alguém sem a chave de assinatura pode criar um segundo commit com os mesmos arquivos, autor e data, além de uma assinatura válida, e o GitHub continua marcando "Verificado".

Tudo o que um revisor verificaria corresponde. O hash do commit não. Isso é relevante porque muitos sistemas tratam o hash de um commit verificado como um nome permanente e único para seu conteúdo.

Aqui está a falha concreta: bloquear um commit malicioso pelo seu hash, e um atacante pode reenviar o mesmo conteúdo sob um hash novo, ainda "Verificado", que nunca esteve na sua lista de bloqueios. Sistemas de deduplicação, registros de procedência e registros de builds reproduzíveis que dependem do hash herdam a mesma vulnerabilidade.

Um espelho comprometido ou hostil pode fornecer a cloneiros commits assinados válidamente cujos hashes diferem dos que estão na forja canônica.

O que isso não é é uma forma de inserir código diferente sem quebrar a verificação de assinatura. Os arquivos são idênticos em cada cópia, então um hash que você definiu ainda busca exatamente o conteúdo esperado, ou falha.

Não há CVE nem aviso de fornecedor, e nada a mudar no seu próprio repositório: a falha está na forma como uma forja decide o que "Verificado" significa, e a correção pertence ao lado da forja.

O trabalho é de Jacob Ginesin, estudante de PhD na Carnegie Mellon University e auditor criptográfico na Cure53. O artigo de cinco páginas, publicado no arXiv em 2 de julho, vem acompanhado de uma ferramenta pública que executa os três ataques, além de dois repositórios de demonstração onde os commits manipulados ainda mostram "Verificado" no GitHub.

Como cada commit nomeia seu pai pelo hash, manipular um commit força novos hashes nos commits acima dele. A ferramenta reescreve essa cadeia para mantê-la consistente. Um descendente assinado, porém, perde seu selo no momento em que seu ponteiro pai é alterado. Ginesin chama esse efeito de "maleabilidade de cadeia de hashes".

A causa é a maleabilidade de assinaturas. O hash de um commit é calculado sobre tudo que está dentro dele, incluindo os bytes brutos da assinatura em seu cabeçalho. Muitas assinaturas podem ser reescritas em uma forma diferente, mas ainda válida, e alterar esses bytes muda o hash sem tocar em nenhuma linha de código.

As três rotas cobrem todos os esquemas GPG que o GitHub verifica, além de S/MIME:

  • Chaves ECDSA (Elliptic Curve Digital Signature Algorithm): inverter a assinatura com uma operação clássica de álgebra de curvas elípticas (transformar o valor s em n - s). Ambas as formas são válidas. Isso passa em uma verificação local com git verify-commit e ganha um selo do GitHub.
  • Chaves RSA e EdDSA: adicionar um campo extra, ignorado, à seção "não hashada" da assinatura, a parte que a assinatura deliberadamente não cobre. A assinatura ainda é válida, mas os bytes do commit, e seu hash, mudam. Verificações locais e do GitHub aceitam.
  • Chaves S/MIME (X.509): reescrever um campo de tamanho na estrutura DER da assinatura para uma forma mais longa e não padronizada. Uma verificação local estrita (via gpgsm) a rejeita, mas o GitHub ainda marca como "Verificado" — ambos os comportamentos são reproduzidos pela ferramenta.

As três rotas compartilham um facilitador: o GitHub não normaliza uma assinatura antes de verificá-la. Sem codificação estrita para S/MIME, sem remoção daqueles campos OpenPGP, e valores ECDSA não canônicos aceitos como estão.

O GitHub então registra um registro "Verificado" contra cada hash de commit e não o verifica novamente, então um commit permanece "Verificado" mesmo depois que sua chave de assinatura é revogada. Envie o original e seu gêmeo para dois branches, e a visualização de comparação do GitHub trata-os como históricos divergentes, um commit à frente e outro atrás, apesar dos arquivos idênticos.

Para deixar claro: isso não é uma colisão de hashes. Não quebra SHA-1 ou SHA-256, e não tem nada a ver com a migração do Git para SHA-256. Ninguém está forçando dois commits diferentes a compartilhar um hash; é o contrário, um commit que pode ser escrito de muitas formas válidas, cada uma com seu próprio hash.

O movimento central é antigo. O Bitcoin lutou contra a mesma simetria ECDSA anos atrás, quando qualquer um podia inverter o valor s em uma assinatura de transação e mudar o ID da transação sem a chave do dono. A correção foi aceitar apenas a forma "S baixo", e depois mover as assinaturas para fora do ID com o SegWit.

As correções propostas no artigo ecoam isso: canonizar a codificação antes de confiar no hash. Uma lição conhecida, não criptografia nova e exótica.

O artigo também conecta isso aos sequestros recentes de tags do GitHub Actions, os ataques de 2025 ao tj-actions/changed-files e de 2026 ao trivy-action (cita este último). Depois daqueles, o conselho foi simples: definir um commit hash completo, não uma tag móvel. Esse conselho ainda se mantém.

Definir o hash bloqueou aqueles ataques, e esta pesquisa não muda isso. Seu ponto é mais restrito. No caso do Trivy, os commits maliciosos se destacavam porque não podiam ser assinados válidamente. Isso é um aviso contra confiar demais nessa indicação: uma assinatura válida prova quem assinou um commit, mas não torna o hash do commit um nome único para o que ele contém.

Então quem precisa fazer algo? Não o desenvolvedor que define um Action ou módulo por hash; um hash definido ainda busca o código certo. O trabalho é para as forjas. O artigo diz que elas devem canonizar as assinaturas antes de confiar nelas.

Ferramentas que bloqueiam, deduplicam ou registram procedência pelo hash de commit devem fazer o mesmo, verificando e canonizando primeiro em vez de confiar no hash bruto de um objeto assinado que um atacante pode recodificar. Nem todos os sistemas estão igualmente expostos: esquemas que também definem um hash independente dos arquivos buscados, como as derivações de saída fixa do Nix, mantêm uma proteção; aqueles que param no hash de commit verificado, não.

Ginesin diz que reportou o problema ao GNU e ao Git em janeiro e ao GitHub em março, e que até a publicação do artigo, nem o Git nem qualquer forja haviam abordado a questão. A correção do lado da forja é bem compreendida, e o lugar óbvio para começar é o caso S/MIME, onde o GitHub ainda aceita o que uma verificação local estrita rejeita.

Article The Hacker News thehackernews.com