Блог

Звонок поступает изнутри вашего конвейера: как устроена атака Codecov

В январе 2021 года злоумышленник добавил одну строку кода в популярный скрипт bash. Десятки тысяч организаций скачали этот скрипт и запускали его в рамках своего обычного рабочего процесса непрерывной интеграции. При каждом запуске, при каждой фиксации, в каждом конвейере скрипт добросовестно отправлял все переменные среды из CI-раннера на неизвестный IP-адрес. Это продолжалось шестьдесят один день, пока кто-нибудь не заметил.

Прошедшие с тех пор годы не оправдали надежду на то, что это был единичный случай. Бэкдор XZ Utils 2024 года, обнаруженный случайно, когда инженер Microsoft заметил, что его входы по SSH происходили с задержкой в полсекунды, был делом рук терпеливого разработчика, который потратил примерно два года на завоевание доверия, прежде чем встроить путь для удаленного выполнения кода в библиотеку сжатия, входящую почти во все дистрибутивы Linux. 

Захват polyfill.io, произошедший несколькими месяцами позже, за считанные дни после смены владельца домена превратил скрипт, встроенный в сотни тысяч сайтов, в сеть распространения вредоносного ПО. Реестры npm и PyPI превратились в постоянный поток скомпрометированных пакетов, которые уже никто и не удосуживается подсчитывать. Codecov больше не является предупреждающим примером. Это - шаблон.

«Codecov - это уже не просто предостерегающая история. Это шаблон».

Интересно в этой истории не то, что компания под названием Codecov пережила несколько тяжелых месяцев в 2021 году. Интересна модель безопасности, которая сделала эту атаку возможной, и это не единичное отклонение, характерное только для одного поставщика. Это структурная особенность того, как создается практически всё современное программное обеспечение.

Тезис прост: конвейер - это периметр. Ров находится не перед производственной средой. Ров окружает то, что развертывает производственную среду.

Проблема периметра

На протяжении двадцати лет архитектура безопасности рассматривала производственную среду как замок, а всё, что за его пределами, - как пустошь. Брандмауэры, WAF, сегментация сети и политики IAM в основном направлены наружу. Им можно доверять ровно настолько, насколько их можно бросить, а учитывая их неосязаемый характер, это не очень далеко.

Проблема в том, что современная производственная среда - это не замок. Это конструкция. Комиты кода превращаются в артефакты, артефакты - в развертывания, а развертывания - в работающую инфраструктуру с доступом к базе данных клиентов. На каждом этапе этой цепочки автоматизированные системы хранят учетные данные с привилегиями, которые, если не присмотреться слишком внимательно, фактически сводятся к «делай, что хочешь».

Ваш CI-раннер хранит ваши ключи AWS. У вашего агента сборки есть учетные данные для реестра контейнеров. Ваш этап развертывания может взаимодействовать с Kubernetes. GitHub Action стажера может, в принципе, отправлять данные в main. Если хотя бы один из этих элементов будет скомпрометирован, различие между «производственной» средой и остальными станет чисто семантическим.

«Каждая новая команда, каждый новый инструмент и каждая новая интеграция расширяют круг субъектов, способных получить доступ к вашим секретам, а конвейер почти никогда не подвергается такому же управлению, какое уже существует для инфраструктуры, в которую он развертывается».

Инцидент с Codecov - это типичный пример, но он не является исключением. Это просто то, что происходит, когда происходит взлом инструмента, обладающего привилегиями конвейера. При наличии достаточного количества инструментов и времени взлом становится статистической неизбежностью. Масштаб - это переменная, которую никто не учитывает в бюджете. Каждая новая команда, каждый новый инструмент и каждая новая интеграция расширяют круг субъектов, способных получить доступ к вашим секретам, а конвейер почти никогда не подпадает под тот же уровень контроля, который уже обеспечивается для инфраструктуры, в рамках которой он работает.

Что произошло

Codecov создал небольшой скрипт на bash под названием Bash Uploader. Он собирал отчёты о покрытии кода из запусков CI и отправлял их обратно в Codecov для обработки. Чтобы использовать его, нужно было добавить одну строку в конфигурацию CI: curl для загрузки скрипта, который передавался напрямую в bash.

Скрипт размещался в корзине Google Cloud Storage. Компания Codecov также создавала и публиковала образы Docker для своего продукта, предназначенного для самостоятельного хостинга, и где-то в промежуточных слоях одного из этих общедоступных образов они встроили ключ HMAC для учётной записи службы Google Cloud Storage, управлявшей этой корзиной. Такие ошибки случаются с людьми, у которых слишком много дел.

31 января 2021 года злоумышленник извлек этот ключ из слоя Docker, использовал его для изменения скрипта Bash Uploader прямо в корзине и начал предоставлять модифицированный скрипт каждому клиенту, который его скачивал. Изменение заключалось в добавлении одной строки примерно на 525-й строке скрипта из 1 800 строк, что, как ни странно, является тем участком, где большинство людей перестают читать код, который якобы выполняет что-то скучное с покрытием кода. Эта строка запускала команду env, которая выводит все переменные среды в текущем шелле, и отправляла вывод методом POST на сервер, контролируемый злоумышленником.

CI-раннеры, как правило, содержат всю конфиденциальную информацию, принадлежащую организации: ключи доступа к AWS, токены GitHub, ключи развертывания, учетные данные баз данных, веб-хуки Slack и ключи API для всех SaaS-сервисов, с которыми интегрирован конвейер развертывания. Измененный скрипт добросовестно выводил все эти данные при каждом запуске в течение двух месяцев у десятков тысяч клиентов.

Взлом был обнаружен 1 апреля 2021 года. Обнаружила его ни Codecov, ни система обнаружения вторжений. Это сделал один из клиентов, который случайно сверил контрольную сумму загруженного скрипта с SHA256, опубликованной Codecov, заметил, что они не совпадают, и написал в Codecov по электронной почте, чтобы спросить, было ли это сделано намеренно.

В список подтвержденных последующих жертв входят HashiCorp, Twilio, Rapid7 и Confluent. Фактический масштаб утечки неизвестен, поскольку после того, как похищенные учетные данные используются для клонирования частных репозиториев, след пролегает через чужие среды, собирая их секретные данные.

Структурная проблема, которую это выявляет

Заманчиво отнести случай с Codecov к категории «ошибки гигиены образов Docker» и не обращать на это внимания. Это ошибка. Проблема с слоем Docker заключается в том, как злоумышленник проник внутрь. Но это не причина, по которой атака оказалась столь разрушительной.

Атака оказалась разрушительной из-за допущения, заложенного практически в каждый конвейер CI/CD на Земле: что любому инструменту, который вы согласились запускать в конвейере, можно доверять все секреты, которыми он располагает.

Это предположение не является абсурдным. Просто оно не обеспечивается на практике. Типичный CI-раннер не имеет четкого понятия о том, что «этот шаг может видеть секрет A, но не секрет B» или «этот шаг может обращаться к AWS, но не к случайному IP-адресу в другой стране». Большинство конвейеров передают полный набор учетных данных каждому запускаемому шагу, руководствуясь вполне разумной на первый взгляд теорией о том, что эти шаги были настроены людьми, работающими в компании.

«Атака оказалась разрушительной из-за предположения, заложенного практически в каждый конвейер CI/CD на Земле: что любому инструменту, который вы разрешили запускать в конвейере, можно доверять все секретные данные, которыми он располагает».

Любой инструмент, который вы curl | bash включаете в свой раннер, наследует привилегии вашего конвейера. То же самое касается любого GitHub Action, на который есть ссылка по тегу. То же самое касается любого образа контейнера, который вы загружаете. То же самое касается любого вспомогательного скрипта, который оказался в зависимости ещё три релиза назад, когда никто не уделял этому особого внимания. Список объектов, которые могут читать ваши переменные среды, - это список всего, что запускается в вашем конвейере, и он гораздо длиннее, чем признают в большинстве проверок безопасности, и с каждым кварталом становится всё длиннее.

Именно поэтому атаки через цепочку поставок не сбавляют обороты с 2021 года. История с Codecov повторяется с вариациями и меняющимся составом участников каждые несколько месяцев в разных экосистемах. Этот тип атак носит структурный характер. И исправление тоже должно быть структурным.

Как выглядит решение

Три меры управления, принятые в совокупности, превратили бы инцидент с Codecov в сноску. Ни одна из них не является чем-то экзотическим. Все они требуют отношения к конвейеру как к производственной инфраструктуре, а не как к средству удобства для разработчиков.

Проверяйте, что на самом деле запускается. Клиент, который в итоге обнаружил атаку, сделал это путем сравнения контрольной суммы SHA256. Codecov публиковал этот хеш. Практически никто его не проверял. Конвейер не должен выполнять произвольно загруженный код (скрипты, контейнеры, провайдеры Terraform, GitHub Actions) без проверки подписей или контрольных сумм по отношению к известному, доверенному источнику. Привязка действий к SHA-коду коммита, а не к тегу, проверка подписей провайдеров и запуск статических сканеров над вашей IaC до того, как какое-либо развертывание затронет облачную учетную запись, - это уже не специализированные практики. Сканеры, такие как Checkov, TFsec, Trivy и TFLint, могут запускаться в качестве этапа непосредственно в конвейере, до того как будет затронуто какое-либо состояние. Это минимальные требования. Цена за то, что их считают необязательными, - двухмесячные окна для утечки данных.

Ограничьте, с кем конвейер может обмениваться данными. Bash Uploader осуществлял утечку данных, отправляя исходящий HTTPS-запрос изнутри CI-раннера на произвольный интернет-хост. Большинство CI-раннеров могут это делать, потому что никто не запретил им это. Самостоятельно размещённые или иным образом контролируемые среды, работающие внутри частной сети с исходящим трафиком, ограниченным известным списком разрешённых адресов, превращают попытку «отправить переменную среды POST на случайный сервер» в неудачный сетевой вызов. Раннер, который может обращаться только к вашей системе контроля версий (VCS), вашим облачным API и заявленному набору внешних зависимостей, не может быть перепрофилирован в универсальный инструмент для вывода учетных данных, независимо от того, что незаметно вставляется в один из его скриптов.

Перестаньте передавать конвейеру статические секреты. Это самое важное изменение, и именно его отрасль осваивает медленнее всего. Причина, по которой команда env была столь эффективным инструментом для злоумышленника, заключается в том, что большинство сред CI переполнены долговечными статическими учетными данными: ключ доступа к AWS, действительный в течение многих лет, или PAT GitHub, действующий до тех пор, пока кто-нибудь не вспомнит о его смене. Федеративная идентификация полностью меняет суть проблемы. С OIDC конвейер не хранит никаких учетных данных. Он предоставляет подписанное утверждение, описывающее, в какой среде он находится, какой рабочий процесс запущен и какой коммит развертывается, а облачный провайдер обменивает это утверждение на кратковременный токен, действие которого ограничено именно той операцией, которая вот-вот произойдет. Утечённое утверждение становится бесполезным уже через несколько минут. Утечённый токен теряет силу в течение часа и не может быть использован за пределами условий, описанных в утверждении.

Конвейер, который проходит аутентификацию в AWS, Azure, GCP и Vault через OIDC, работает внутри сегмента сети с белым списком исходящего трафика и проверяет артефакты и поставщиков, от которых он зависит, - это конвейер, в котором сценарий Codecov генерирует вредоносный скрипт, способный сделать очень мало. env вывод не содержит секретов с длительным сроком действия. Пункт назначения для вывода данных недоступен. Измененный скрипт не проходит проверку ещё до запуска.

Эти меры контроля не предотвращают первоначальный взлом. Они делают первоначальный взлом неинтересным, что является вторым лучшим вариантом и, в большинстве случаев, достижимой целью.

Относитесь к конвейеру как к производственной среде

Есть ещё одно изменение, и оно носит скорее культурный, чем технический характер. Конвейер нуждается в том же управлении, которое уже применяется к производственной среде. Это означает журналы аудита для каждого развертывания, политики утверждения (желательно «политика как код») для изменений, затрагивающих чувствительные области, и доступ на основе ролей, чтобы разработчик не мог по неосторожности предоставить конвейеру больше привилегий, чем позволяет его собственная роль. Это также означает обнаружение отклонений, благодаря чему разрыв между тем, что записано в коде, и тем, что фактически выполняется, перестает быть «неписаным правилом» и становится отслеживаемым фактом, о котором можно получать оповещения.

«Эти меры контроля не предотвращают первоначальный взлом. Они делают первоначальный взлом неинтересным».

В этом нет ничего привлекательного. Но именно в этом заключается разница между системой CI/CD, представляющей собой поверхность атаки, и системой, которая служит точкой обеспечения соблюдения требований.

Если рассмотреть эти четыре меры контроля вместе, они описывают одну и ту же идею. Конвейер заслуживает такого же непрерывного управления, как и инфраструктура, которую он развертывает: не разового проверки безопасности, а контроля, который действует до запуска изменения, во время его выполнения и после его внедрения. Проверка того, что запускается, ограничение мест, с которыми система может взаимодействовать, удаление статических учетных данных и управление каждым развертыванием с помощью журналов аудита и обязательных к исполнению политик - это не просто функции безопасности, прикрученные к платформе. Это управление, примененное к той части стека, которая долгое время рассматривалась как средство удобства, и применяемое одинаково независимо от того, запускает ли команда десять конвейеров или десять тысяч.

Ваш конвейер - это один из слоёв вашего периметра, и на данный момент он является самым уязвимым. На протяжении большей части своего существования он рассматривался скорее как инструмент повышения производительности, чем как граница безопасности. Злоумышленники из Codecov поняли это в 2021 году. Следующая группа злоумышленников, сканирующая общедоступные образы Docker в поисках лишних уровней и действующая сейчас быстрее, чем кто-либо пять лет назад, тоже это понимает. Если мы не поймём этого так же хорошо, нам придётся платить всё более высокую цену.

Подписывайтесь на наш канал в Телеграм

Подписаться
CI/CD security