⚠️
CVE-2026-42501 или почему вам нужно обновиться до Go 1.26.3/go1.25.10 ⚠️
Те кто давно работают с Go (или
читают меня) знают, что целостность наших модулей гарантируется не только HTTPS транспортом до самой прокси, но и отдельной базой SumDB, которая хранит в себе хеши модулей подписанные приватным ключом. При этом сама база устроена так, что невозможно внести изменения в прошлые записи, без каскада изменений в новые, тк каждый блок «подписан» предыдущим.
Но в ИБ сама безопастность это устойчивость самого слабого звена в цепочке. Достаточно одной маленькой ошибки, что-бы вся надежно выстроенная архитектура ИБ превратилась в пыль. Так и случилось в нашем случае.
Рассмотрим сценарий: при получении модуля от прокси, Go проверяет его хеш с использованием SumDB, блоки которой, как я уже говорил выше, подписаны приватным ключом. В псевдокоде это выглядит следующим образом:
for _, line := range sumdbResponse {
if line относится к нужному module@version {
if hash не совпадает {
return SECURITY ERROR
}
}
}
return nil
На первый взгляд, всё кажется логичным: если пришел ответ, проверяем хеш нужного модуля и при несовпадении валимся с ошибкой. При совпадении выходим из функции и продолжаем работу. Однако здесь кроется одна маленькая, но критичная деталь: что будет если нам вернут ответ без записей или запись про другой модуль? Скомпроментированная прокся может отдать специально подготовленный ответ, который наш клиент воспримет как правильный — достаточно выдать ответ с корректными хешами для любых модулей или даже выдать ответ без хешей. Поскольку go get пишет записи в go.sum с локально подсчитанного хеша, подмену никто не заметит, и тулинг спокойно продолжит работу, положив модуль от скомпрометированной прокси в go.mod/go.sum. Который, в свою очередь, будет использован компилятором при сборке проекта.
Т.е. перед нами классическая уязвимость которая позволяет организовать Supply Chain Attack (атака на зависимости), пример которой можно описать вот так:
1. Прокся отдаёт клиенту
изменённый архив модуля, например
rsc.io/foo@v1.0.0.
2. На запрос хеша возвращается валидный, криптографически корректный ответ от checksum database, но для другого модуля, например
rsc.io/bar@v1.0.0.
3. go get видит: «ответ sumdb валидный, несовпадающего хэша нет» — и
продолжает работу.
Т.е. вы получаете измененный модуль, у которого хеш оригинала можно узнать только после удаления go.sum и изменения прокси или использования direct в GOPROXY.
Проблема бы не была такой большой (ибо у большинства переменную GOSUMDB, отвечающую за источник правды о хешах, никто не трогает, а для загрузки тулчейна её вообще
невозможно переопределить) если бы не два но:
• Директивы toolchain и go позволяют выбирать нужную версию компилятора автоматически, и при необходимости, скачивать её с той же самой прокси.
• go get
всегда сначала спрашивает проксю, может ли та проксировать запросы до базы хешей GOSUMDB. Если ответ положительный, то она просто спросит её о хеше, вместо обращения непосредственно к GOSUMDB.
В этой ситуации фактически всю цепочку доставки модулей до клиента (и тулчейнов, если
явно не запрещен автовыбор) контролирует один узел. Поэтому, если у вас изменена GOPROXY или вы не доверяете сертификатам TLS установленным в системе, то я очень рекомендую вам обновиться. При этом вам недостаточно обновить версию go (или toolchain) в файле go.mod, ведь изначальный тулчейн будет по-прежнему работать по старой логике. Нужно именно установить новую версию компилятора, заменив ту которая стоит «по умолчанию» в системе, а затем прогнать rm go.sum && go mod tidy в проектах которые могут быть скомпрометированы.
При всей опасности атаки, фикс у неё самый тривиальный: return nil просто заменили на
return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum missing from sumdb response"+sumdbAbsent, noun))
П.С. Именно поэтому я рекомендую использовать приложения типа
direnv для установки переменных GOPROXY или GOPRIVATE только для рабочих проектов.