Зелёная галочка GitHub «Verified» обманывает: подписанный коммит можно переписать в другой хеш

Новое исследование ставит под сомнение одну из базовых гарантий, на которую годами опирались разработчики: считалось, что хеш подписанного Git-коммита — уникальный, неповторимый идентификатор его содержимого. Оказалось, что это не так. Не имея закрытого ключа автора, атакующий способен создать второй коммит с теми же файлами, тем же автором, той же датой и валидной подписью — и GitHub всё равно пометит его как «Verified».

В чём проблема

Всё, что обычно проверяет ревьюер, совпадает: файлы, автор, дата, подпись. Не совпадает только хеш коммита. А ведь именно на хеш опираются многие системы, воспринимая его как постоянное уникальное имя содержимого.

Вот конкретный сценарий отказа: заблокировав вредоносный коммит по его хешу, защитник ничего не выигрывает — атакующий может протолкнуть тот же контент под свежим, всё ещё «Verified» хешем, которого нет в блок-листе. Тем же изъяном страдают системы дедупликации, журналы происхождения (provenance) и записи воспроизводимых сборок, которые используют хеш как ключ. Скомпрометированное или враждебное зеркало репозитория может выдать клонирующим валидно подписанные коммиты с хешами, отличающимися от тех, что на каноническом форже.

Важная оговорка: это не способ подменить код под видом чужой подписи. Файлы во всех копиях идентичны, поэтому запиненный хеш всё так же либо получает ожидаемое содержимое, либо не срабатывает вовсе. CVE и вендорского advisory по проблеме нет — менять что-либо в собственном репозитории не нужно, дефект кроется в том, как форж определяет понятие «Verified», и чинить его должна именно площадка.

Кто нашёл и как

Работу провёл Джейкоб Гинезин (Jacob Ginesin), аспирант Университета Карнеги — Меллона и криптографический аудитор компании Cure53. Его пятистраничная статья опубликована на arXiv 2 июля вместе с публичным инструментом, реализующим все три атаки, и двумя демонстрационными репозиториями, где переработанные («malleated») коммиты по-прежнему показывают «Verified» на GitHub.

Поскольку каждый коммит ссылается на родителя по хешу, изменение одного коммита вынуждает менять хеши всех коммитов выше по цепочке. Инструмент автора перестраивает цепочку так, чтобы она оставалась согласованной. Правда, подписанный потомок теряет собственную «галочку», как только у него меняется указатель на родителя. Гинезин называет этот эффект «malleability цепочки хешей» (hash chain malleability).

Три вектора атаки

Причина — податливость подписи (signature malleability): хеш коммита вычисляется по всему его содержимому, включая сырые байты самой подписи в заголовке. Многие схемы подписи допускают перезапись в иную, но всё равно валидную форму — и изменение этих байтов меняет хеш, не трогая ни строчки кода. Три маршрута атаки покрывают все GPG-схемы, которые проверяет GitHub, плюс S/MIME:

  • Ключи ECDSA — переворот подписи классической операцией эллиптической кривой (значение s заменяется на n − s). Обе формы валидны. Такой коммит проходит и локальную проверку git verify-commit, и получает бейдж GitHub.
  • Ключи RSA и EdDSA — в «неподписываемую» часть подписи (unhashed section), которую сама подпись сознательно не покрывает, добавляется лишнее игнорируемое поле. Подпись по-прежнему проходит проверку, но байты коммита — а с ними и хеш — меняются. Принимают и локальная утилита, и GitHub.
  • Ключи S/MIME (X.509) — поле длины в DER-структуре подписи переписывается в более длинную, нестандартную форму. Строгая локальная проверка через gpgsm такой коммит отклоняет, а GitHub всё равно помечает его «Verified» — оба поведения воспроизводит инструмент автора.

Общий знаменатель всех трёх маршрутов — GitHub не нормализует подпись перед проверкой: нет строгого кодирования для S/MIME, нет отсечения лишних полей OpenPGP, неканонические значения ECDSA принимаются как есть. После этого GitHub заносит запись «Verified» именно по хешу коммита и повторно её не перепроверяет — поэтому коммит остаётся «Verified» даже после отзыва подписывающего ключа. Если протолкнуть оригинал и его «двойника» в две разные ветки, интерфейс сравнения GitHub покажет их как расходящиеся истории — один коммит впереди, другой позади, — хотя файлы полностью идентичны.

Это не коллизия хешей

Важно подчеркнуть: речь не о коллизии хешей. SHA-1 и SHA-256 не сломаны, и к переходу Git на SHA-256 находка отношения не имеет. Никто не заставляет два разных коммита делить один хеш — наоборот, один и тот же коммит может быть записан множеством валидных способов, каждый со своим хешем.

Сам приём не нов: биткоин годами боролся с той же симметрией ECDSA — любой мог перевернуть значение s в подписи транзакции и изменить её идентификатор без ключа владельца. Тогда решением стало принимать только «low-S»-форму, а позже подписи вовсе вынесли из идентификатора транзакции с помощью SegWit. Рецепт из новой статьи созвучен: канонизировать кодирование до того, как доверять хешу — известный урок, а не экзотическая новая криптография.

Связь с атаками на цепочки поставок

Автор связывает находку с недавними захватами тегов в GitHub Actions — атаками на tj-actions/changed-files в 2025 году и на trivy-action в 2026-м (последняя упомянута в статье напрямую). После тех инцидентов совет был прост: пинить зависимости на полный хеш коммита, а не на подвижный тег. Этот совет по-прежнему работает — пиннинг остановил те атаки, и новое исследование этого не меняет. Его смысл уже: в случае с Trivy вредоносные коммиты выделялись именно тем, что не могли быть валидно подписаны. Новая работа предупреждает не полагаться на этот признак чрезмерно — валидная подпись доказывает, кто подписал коммит, но не превращает его хеш в уникальное имя содержимого.

Что делать

Разработчику, пинящему Action или модуль, ничего менять не нужно — запиненный хеш всё так же доставит именно тот код, что ожидается. Работа предстоит форджам: авторы статьи считают, что площадки должны канонизировать подписи, прежде чем им доверять. То же касается любых инструментов, которые блокируют, дедуплицируют или ведут журнал происхождения по хешу коммита — им стоит сначала проверять и канонизировать, а не доверять сырому хешу подписанного объекта, который атакующий способен перекодировать. Не все системы одинаково уязвимы: схемы, которые дополнительно пинят независимый хеш самих файлов, — например, fixed-output-деривации Nix, — сохраняют страховку. Те, что останавливаются на хеше верифицированного коммита, — нет.

По словам Гинезина, он сообщил о проблеме проекту GNU и разработчикам Git в январе, а GitHub — в марте; на момент публикации статьи ни Git, ни какой-либо форж проблему не устранили. Исправление на стороне форджа хорошо понятно, и очевидная точка для старта — кейс S/MIME, где GitHub принимает то, что отклоняет строгая локальная проверка.

Источник: The Hacker News.