Атаки на цепочки поставок набирают темп: как мы стали аудировать транзитивные зависимости перед обновлениями
После MOVEit и 3CX ввели обязательный аудит транзитивных зависимостей для серверов в контуре КИИ. Рассказываем, зачем это нужно и как выглядит процесс.
Рост числа атак на цепочки поставок Open Source в 2023-2024: тренд на закладки в зависимостях продолжается после инцидентов с 3CX и MOVEit
Если в 2022 году атака на цепочку поставок была поводом для доклада на конференции, то к концу 2023-го это стало рутинным вектором с реальными жертвами. MOVEit, 3CX, GoAnywhere, Cl0p - каждая история по-своему показательна, но между ними есть общее: атака прошла через легитимный канал доставки ПО, который никто всерьёз не проверял.
После нашего разбора атаки на 3CX и итогов аудитов по MOVEit внутри команды возник неудобный вопрос: а мы сами как обновляем пакеты на серверах в КИИ-контуре? Ответ оказался «как обычно» - и это нас не устроило.
Почему «как обычно» недостаточно
Стандартный процесс обновления выглядит примерно так: вышел патч к уязвимости, проверили changelog, убедились что CVE закрыта, обновили на тестовом стенде, раскатили на prod. Для большинства сценариев это разумно и достаточно.
Проблема в транзитивных зависимостях. Когда вы обновляете libssl или liblzma, вы получаете не только их код, но и весь граф зависимостей, который тащится следом. И этот граф никто не читал. Никто не смотрел, кто его майнтейнит, когда последний раз была активная ревью-активность, нет ли там новых коммитов от аккаунтов, которые появились три месяца назад и сразу получили права на мерж.
Именно такой сценарий был реализован в атаках типа SolarWinds и именно к нему всё явнее движется supply chain attack как класс. История с 3CX вообще была двойной цепочкой: сначала скомпрометировали зависимость в инфраструктуре 3CX, а потом уже через них - клиентов. Два слоя транзитивных доверий, ни один из которых нормально не контролировался.
Что мы ввели
После внутреннего обсуждения ввели обязательный шаг аудита зависимостей перед любым обновлением пакетов на серверах, которые входят в контур КИИ или обрабатывают чувствительные данные. Процесс не героический и не автоматизированный полностью - это намеренно, потому что полностью автоматизированный процесс создаёт ложное ощущение покрытия.
Первое - инвентаризация обновляемого стека. Перед обновлением генерируем список всего, что изменится, включая транзитивные зависимости. apt list --upgradable с полным деревом, или аналог для RPM-based. Не «какие пакеты обновятся» - а «какие библиотеки они подтянут и в каких версиях».
Второе - выборочная проверка репозиториев. Для пакетов, которые не обновлялись давно и вдруг получили свежий коммит перед релизом, смотрим историю: кто коммитил в последние три-шесть месяцев, есть ли новые аккаунты с правами мержа, не изменилась ли структура сборочного процесса. Это занимает время - именно поэтому мы делаем это только для КИИ-контура, не для всего подряд.
Третье - верификация сборочных артефактов. Там, где есть подписи - проверяем подписи. Где есть SBOM - сравниваем с предыдущей версией и смотрим на расхождения. Где нет ничего - это уже само по себе сигнал для дополнительного внимания.
Четвёртое - временное окно. Для критичных систем не обновляем в первые 48-72 часа после релиза. Логика простая: если закладка активная, за это время её скорее всего заметят и поднимут шум в community. Если тихо - обновляем.
Где это реально работало
Один конкретный случай без лишней детализации: в октябре при аудите зависимостей перед плановым обновлением одного из серверов обнаружили, что пакет, который планировался к обновлению, потащил за собой библиотеку с историей коммитов, заметно изменившейся за последние два месяца - новый контрибьютор с активностью строго по ночному времени UTC и несколько коммитов с правкой сборочных скриптов. Закладки там не оказалось - при более детальном анализе всё объяснилось. Но именно такая комбинация признаков и есть то, на что нужно смотреть. Упражнение было небессмысленным.
Что это не решает
Честно: полностью эту проблему закрыть нельзя. Если атака хорошо подготовлена и изменения внесены так, что выглядят как нормальный рефакторинг от давно знакомого контрибьютора - при ручном аудите это пропустишь. SolarWinds именно так и работал: подозрительный код был написан в стиле существующей кодовой базы, изменения распределены по нескольким коммитам.
Наш процесс снижает риск, не устраняет его. Это стоит понимать явно, чтобы не создавать у клиентов иллюзию полной защиты. Иллюзия - это отдельный риск.
Накладные расходы
Вопрос, который задают всегда: сколько это занимает времени? Честный ответ: от двух часов до половины рабочего дня в зависимости от размера стека и количества «нестандартных» пакетов. Для крупных обновлений с десятками зависимостей - дольше. Для точечных патчей одного сервиса - быстрее.
Мы не распространяем этот процесс на всё подряд - только на серверы в КИИ-контуре и на системы с персональными данными выше базового уровня. Для остальной инфраструктуры достаточно стандартного patch management. Иначе накладные расходы становятся неприемлемыми, а команда начинает срезать углы там, где не должна.
Тренд на атаки через зависимости продолжается. Насколько он разовьётся - посмотрим. Пока процесс описан, зафиксирован в регламенте и работает.