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

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

Автор работы — Джейкоб Гинесин, аспирант Университета Карнеги-Меллон и криптографический аудитор компании Cure53. 2 июля он опубликовал на arXiv пятистраничную статью с описанием проблемы, приложил рабочий инструмент, реализующий все три варианта атаки, и выложил два демонстрационных репозитория, где «переродившиеся» коммиты по-прежнему помечены как проверенные.
Механизм называется «malleability цепочки хешей» — податливость к изменению. Каждый коммит в Git ссылается на родителя по хешу, поэтому изменение одного коммита автоматически меняет хеши всех потомков. Инструмент Гинесина перестраивает всю цепочку заново, чтобы сохранить консистентность. Правда, есть нюанс: как только у потомка меняется указатель на родителя, сам потомок теряет бейдж верификации — трюк работает только с конкретным целевым коммитом, а не бесконечно вниз по истории. Корень проблемы — в том, что хеш коммита считается по всему его содержимому, включая сырые байты подписи. А многие схемы подписи допускают перекодирование в другую, но всё ещё валидную форму. Меняются байты подписи — меняется хеш — код остаётся прежним.
Гинесин описал три маршрута атаки, покрывающих все схемы, которые GitHub помечает как проверенные.
Первый — ключи ECDSA. Здесь используется классическая алгебра эллиптических кривых: значение подписи s можно заменить на n − s, и обе формы останутся математически валидными. Такая подпись проходит и локальную проверку через git verify-commit, и получает бейдж на GitHub.
Второй — ключи RSA и EdDSA. В «нехешируемую» часть подписи (ту, что намеренно не покрывается самой подписью) добавляется лишнее поле, которое просто игнорируется при проверке. Подпись остаётся валидной, а байты коммита — и, соответственно, его хеш — меняются. Принимается и локально, и на GitHub.
Третий, самый показательный случай — ключи S/MIME на базе X.509. Здесь переписывается поле длины в DER-структуре подписи в нестандартную, но технически допустимую более длинную форму. Строгая локальная проверка через gpgsm такую подпись отклоняет. А GitHub — принимает и помечает как «Verified». Это расхождение инструмент Гинесина воспроизводит напрямую.
Общая причина всех трёх сценариев одна: GitHub не нормализует подписи перед их проверкой. Нет строгого контроля кодировки для S/MIME, нет удаления неаутентифицированных полей OpenPGP, нестандартные (не в канонической форме) значения ECDSA принимаются как есть.
К этому добавляются и другие особенности поведения платформы. GitHub фиксирует статус «Verified» привязанным к конкретному хешу коммита и никогда не перепроверяет его повторно — коммит остаётся «проверенным» даже после того, как ключ подписи был отозван. А если оригинал и его «двойник» запушить в две разные ветки, сравнивалка GitHub воспринимает их как разошедшиеся истории — одна впереди, другая позади, хотя содержимое файлов идентично.
У этой проблемы есть исторический аналог — Bitcoin столкнулся с точно такой же симметрией ECDSA много лет назад: любой человек мог изменить значение s в подписи транзакции и тем самым изменить её идентификатор, не имея ключа владельца. Решали проблему в два этапа: сначала ограничили подписи только «low-S» формой, а затем полностью вынесли подписи за пределы идентификатора транзакции через SegWit. Рекомендация из новой статьи звучит почти так же: канонизировать кодировку до того, как доверять хешу. Сам Гинесин называет это «известным уроком, а не экзотической новой криптографией».
Работа напрямую связана с недавними атаками на цепочки поставок через GitHub Actions — угоном тегов в проектах tj-actions/changed-files (2025 год) и trivy-action (2026 год). После этих инцидентов стандартной рекомендацией стало закрепление зависимостей на полный хеш коммита вместо изменяемого тега — и этот совет остаётся полностью верным, новое исследование его не отменяет. В случае с trivy-action вредоносные коммиты удалось выявить именно потому, что их не получалось подписать валидно. Но в этом и заключается предостережение Гинесина: не стоит слишком полагаться на подпись как на индикатор безопасности. Валидная подпись подтверждает, кто подписал коммит, но не гарантирует, что хеш коммита — уникальный идентификатор его содержимого.
Кто должен на это реагировать? Точно не разработчики, которые закрепляют зависимость на конкретный хеш Action или модуля, — закреплённый хеш всё равно скачает именно тот код, который ожидается, либо просто не сработает. Ответственность лежит на самих форджах — таких как GitHub. Рекомендованное решение — канонизировать подписи перед тем, как им доверять. То же касается любых систем дедупликации, логирования происхождения кода или блокировки по хешу: им стоит сначала проверять и приводить подпись к канонической форме, а не доверять сырому хешу подписанного объекта, поскольку атакующий может его перекодировать.
Не все системы одинаково уязвимы. Те, что дополнительно закрепляют независимый хеш непосредственно скачанных файлов, сохраняют защиту — в статье в качестве примера приводятся fixed-output derivations в Nix. А вот системы, полагающиеся исключительно на верифицированный хеш коммита, остаются открытыми для этой проблемы.
По срокам раскрытия: в январе Гинесин сообщил о находке в GNU и проект Git, в марте — в GitHub. На момент публикации статьи ни Git, ни какой-либо фордж проблему не устранили. Считается, что исправление на стороне форджей не требует чего-то принципиально нового — начать предлагается с кейса S/MIME, где GitHub сейчас принимает подписи, которые строгая локальная проверка через gpgsm отклоняет как невалидные.


Новое на сайте

20327Кости прерий: как истребление бизонов породило целую индустрию — и сама себя же уничтожила 20326Кто и зачем взламывает серверы Ollama и ComfyUI ради ключей от AWS? 20325Как злоумышленники спрятали командный сервер внутри блокчейна и почему его невозможно... 20324Брюссель заставляет Android делиться секретами с чужими ИИ-помощниками 20323WordPress: как два бага слились в одну критическую дыру, которую назвали wp2shell 20322Как китайские хакеры обманули DigiCert и украли сертификаты для подписи кода? 20321Что скрывается за уязвимостью, которую агентство США внесло в список активно используемых... 20320Автономные системы наступают быстрее, чем инфраструктура для управления ими: кто выиграет... 20319Почему в OpenSSL нашли дыру, съедающую память серверов, но не дали ей даже номер CVE? 20317SonicWall SMA 1000: как два бага превратили VPN-шлюз в бэкдор для атакующих 20316Может ли уязвимость в клиенте Zoom для Windows открыть доступ к чужому аккаунту без... 20315TELEPUZ: новый вредонос на C, который научился прятаться в Telegram, Steam и блокчейне... 20314Дома из дёрна: как исландцы триста лет прятались от холода под слоем земли и травы 20313Как один токен от чужого сервиса мог впустить злоумышленника в чужой аккаунт n8n?
Ссылка