ADG Оставить заявку
Блог Информационная безопасность 1 мин чтения

Codecov bash uploader взломан: проверяем CI/CD после очередной supply-chain атаки

Скомпрометированный bash-скрипт Codecov воровал переменные окружения из CI/CD. Проверяем свои пайплайны и переходим на pin версий с проверкой SHA256.

Контекст момента

Codecov bash uploader скомпрометирован: вредоносный код в официальном скрипте загрузки покрытия собирал переменные окружения CI/CD и отправлял их на сторонний сервер

В начале апреля Codecov сообщил, что их официальный bash-скрипт для загрузки отчётов покрытия кода был подменён. Кто-то получил доступ к их инфраструктуре через скомпрометированные credentials от Docker Hub и внедрил в скрипт дополнительную строчку - она собирала переменные окружения CI/CD-воркера и отправляла их на сторонний IP. Это висело незамеченным примерно два месяца - с 31 января по 1 апреля.

Нас это задело напрямую: несколько наших пайплайнов используют Codecov.

Что именно произошло

Вредоносная строка в скрипте выглядела невинно - она просто добавляла в curl-запрос к серверу Codecov содержимое git remote -v и весь вывод env. Всё окружение CI/CD - токены, ключи, credentials от реестров, AWS-ключи, всё что лежит в переменных пайплайна - улетало на сторону.

Вектор элегантный и неприятный. Скрипт подтягивается командой вроде:

bash <(curl -s https://codecov.io/bash)

Это паттерн, который встречается в документации множества сервисов - Codecov, Homebrew, различные установщики. Удобно: одна строчка, всё установится само. Неудобно: вы запускаете произвольный код из интернета с правами текущего пользователя, без какой-либо проверки что именно пришло.

В данном случае скрипт был официальным. Просто в определённый момент он перестал быть тем, чем должен был быть.

Первичный разбор у нас

Когда Codecov опубликовал advisory, первое что мы сделали - посмотрели на свои пайплайны. Несколько проектов используют Codecov для отчётности по тестовому покрытию. Вопрос был простой: какая именно команда стоит в .gitlab-ci.yml?

Оказалось разное. Один проект тянул скрипт напрямую через curl без пиннинга - классический вариант из документации. Другой использовал pinned версию с указанием конкретного тега, но без проверки хеша. Третий вообще не использовал bash uploader - только Python-пакет. Это лотерея.

Для всех проектов с curl-вариантом мы прошлись по переменным окружения CI/CD: что там лежит, что могло утечь в период январь-апрель. В нескольких местах нашлись токены с избыточными правами - не потому что их украли, а потому что так сложилось исторически. Хорошая возможность разобраться.

Токены, которые потенциально могли быть в окружении в период компрометации, ротировали. Перестраховка, но в данной ситуации оправданная.

Корень проблемы: curl | bash без верификации

Скомпрометированный скрипт Codecov - это хорошая иллюстрация проблемы, которую мы сами у себя не разбирали системно: внешние скрипты в CI без верификации целостности.

bash <(curl -s https://example.com/install.sh) удобен. Но у него нет механизма проверки что вы получили именно то, что ожидали. DNS может быть скомпрометирован, CDN может быть взломан, сам сервис может быть скомпрометирован - как в случае с Codecov. Вы узнаете об этом только после того, как код уже выполнился.

Правильный подход тут два варианта, и лучше использовать оба вместе:

Первое - pin конкретной версии. Вместо того чтобы тянуть latest или просто URL без версии, указываем конкретный коммит или тег. Для Codecov это выглядит так:

curl -Os https://uploader.codecov.io/v0.1.13/linux/codecov

Конкретная версия, конкретный бинарь. Он не изменится задним числом - в отличие от curl https://codecov.io/bash, который отдаёт текущую версию скрипта каждый раз.

Второе - проверка SHA256. Перед запуском любого внешнего скрипта или бинаря - проверяем хеш:

curl -Os https://uploader.codecov.io/v0.1.13/linux/codecov
curl -Os https://uploader.codecov.io/v0.1.13/linux/codecov.SHA256SUM
shasum -a 256 -c codecov.SHA256SUM
chmod +x codecov
./codecov

Если скрипт был подменён - хеш не совпадёт, шаг CI упадёт с ошибкой. Это именно то, что должно было случиться с Codecov-скриптом - только у большинства этой проверки не было.

Что мы поменяли в пайплайнах

По всем проектам прошлись и вынесли установку внешних инструментов в отдельный шаг с явной верификацией. Для Codecov перешли на их новый uploader (они его переписали на Go именно после этого инцидента) с пиннингом версии и проверкой хеша.

Параллельно зафиксировали для себя правило: любой внешний скрипт или бинарь в CI - только через pinned версию и SHA256. Без исключений. Это немного дольше при первоначальной настройке и требует обновлять хеш при апгрейде версии - но это сознательный выбор, а не автоматическое доверие.

Отдельно посмотрели на минимизацию переменных окружения. Если job загружает покрытие в Codecov - ему нужен только Codecov-токен. Не AWS-ключи, не токен от registry, не SSH-ключи от серверов. Принцип минимальных привилегий применим не только к пользователям, но и к отдельным шагам пайплайна. CI/CD с широким доступом ко всем credentials сразу - это большая поверхность атаки.

Три supply-chain инцидента за четыре месяца

SolarWinds в декабре, атаки через зависимости npm и PyPI как фоновый шум, теперь Codecov. Атака через цепочку поставок - это уже не редкий сценарий из отчётов APT-исследователей, это практика.

Неприятная особенность Codecov-случая в том, что атака была направлена прицельно на CI/CD - место, где концентрируется доступ ко всему: коду, артефактам, облакам, серверам. Пайплайн с широкими правами и подтягиванием внешних скриптов - хорошая цель.

Конкретные шаги по аудиту CI/CD, которые мы сейчас проходим по клиентским проектам в рамках аудита безопасности: инвентаризация внешних скриптов и бинарей в пайплайнах, аудит переменных окружения на избыточность прав, проверка что критичные credentials не доступны всем job'ам подряд. Не революция - просто то, о чём нужно думать системно, а не после следующего инцидента.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.