open atlas
↑ К треку
Безопасность облака и инфраструктуры CLOUD · 03 · 02

Сканирование образов и зависимостей

Сканеры находят каждый CVE в зависимостях и базовых образах, но большинство не достигает эксплуатируемого кода. Достижимость, серьёзность и наличие фикса — то, чем сеньор превращает стену красного в короткий ранжированный список.

CLOUD Middle ◷ 15 min
Уровень
ОсновыJuniorMiddleSenior

В пятницу ты подключаешь Trivy в CI. Первый же пуш в понедельник роняет сборку: 312 уязвимостей, 41 критическая. Образ — это стоковый node:20 плюс десяток npm-пакетов, которые ты не писал. На выходных никто не трогал криптографию; ты просто включил фонарик, который всё это время светил на кучу долга. Соблазн — поставить --severity HIGH,CRITICAL --exit-code 0 и забыть: оставить свет, но выдернуть сигнализацию. Это неверный рефлекс — и он же самый частый. Настоящая работа не в том, чтобы найти CVE; сканер делает это бесплатно. Работа — решить, какие из 312 реально достижимы и эксплуатируемы в твоём работающем сервисе, потому что 280 из них — никогда.

К концу урока ты будешь понимать, как SCA- и image-сканеры порождают эту стену красного и как достижимость, наличие фикса и серьёзность превращают её в короткий ранжированный список, по которому можно действовать, не теряя чувствительности к алертам.

Два сканера, одна проблема инвентаризации

Образ контейнера — это стопка из двух цепочек поставок ПО, и сканировать нужно обе. Слой ОС — это всё, что приносит базовый образ: glibc, openssl, zlib, сотни пакетов Debian или Alpine, большинство из которых ты никогда не вызываешь. Слой приложения — твои прямые и транзитивные зависимости: package-lock.json, go.sum или requirements.txt, тянущие код, который ты действительно зовёшь. Обе цепочки сканируются одинаково, и стоит точно понять как — потому что механизм объясняет шум.

Сканер не анализирует твой код на ошибки. Он строит ведомость материалов — каждый пакет и точную версию — а затем сопоставляет этот список с базой уязвимостей (NVD плюс дистрибутивные ленты вроде Debian Security Tracker, GitHub Advisories и языковые экосистемы). Вывод — это арифметика множеств: installed_packages ∩ known_vulnerable_versions. Именно из-за этого пересечения свежий образ node:20 сообщает о десятках CVE в первый же день — ты установил ОС с известно-уязвимыми пакетами, и сканер сообщает факт, а не суждение. В руководстве OWASP DevSecOps это называется Software Composition Analysis (SCA), и это самый дешёвый высокоценный контроль, который можно добавить в пайплайн. И это же причина, по которой его включение ощущается как погребение под завалом.

Почему стена красного — в основном шум

Та же арифметика множеств, что делает сканирование дешёвым, делает его шумным. CVE из installed_packages ∩ known_vulnerable_versions говорит, что уязвимый код присутствует. Он не говорит, что код загружен, вызван или достижим с подконтрольным атакующему вводом. Этот разрыв — источник почти всего шума, и у него три различимые формы, которые сеньор учится распознавать.

Три формы шума:

  • Недостижимый код. CVE в NIS-резолвере glibc, в пути разбора шрифтов библиотеки изображений, которую ты используешь только для миниатюр, или в подкоманде CLI, которую сервис никогда не вызывает. Уязвимые байты лежат на диске; уязвимый путь мёртв. Анализ достижимости — достигает ли хоть одна цепочка вызовов от твоей точки входа уязвимой функции? — это крупнейший единичный редуктор шума, и новейшие сканеры (и стандарт VEX, Vulnerability Exploitability eXchange) существуют именно чтобы записывать «присутствует, но не затронут».
  • Фикса ещё нет (или won't-fix). Дистрибутивы рутинно помечают низкосерьёзные CVE как won't-fix, потому что фикс не стоит риска регрессии. Ронять сборку на CVE без апстрим-патча означает, что сборка никогда не позеленеет — ты построил храповик, который только затягивается.
  • Серьёзность ≠ эксплуатируемость в контексте. CVSS 9.8 в dev-зависимости, вырезанной tree-shaking из продакшен-бандла, — это не 9.8 для тебя. CVSS оценивает уязвимость в абстракции; только ты знаешь, экспонирована ли она.
НаходкаCVSSДостижим?Фикс?Действие сеньора
RCE в JSON-либе, вызываемой на каждый запрос9.8ДаПатч естьБампнуть сейчас; это и есть алерт
RCE в dev-CLI, вырезанной из прода9.8НетПатч естьБампнуть попутно; не гейтить
DoS в NIS-пути glibc, который не вызывают7.5Нетwon’t-fixПодавить через VEX + обоснование
Обход аутентификации в твоём web-фреймворке8.1ДаПатча нетКомпенсирующий контроль + отслеживать

Где ставить гейт и на что он гейтит

Понимание форм шума позволяет поставить гейт правильно. Неверный дизайн — тот, к которому тянутся первым, — это «ронять сборку, если есть хоть одна CRITICAL». Он даёт три сбоя на первой же неделе: сборки, которые не могут позеленеть (CVE без фикса), усталость от алертов (так что единственный реальный RCE проскальзывает мимо вместе с 40 недостижимыми) и неизбежный --exit-code 0, тихо отключающий весь контроль. Тихо обезвреженный сканер хуже, чем его отсутствие, потому что позволяет верить, что ты защищён.

Гейт сеньора собирает три предиката: серьёзность И наличие фикса И не-подавлено. Ронять сборку только на исправимой, high-или-critical, неподавленной находке — потому что это то множество, где падение сборки реально меняет чьё-то поведение (бампни версию). Всё остальное идёт в другой поток: отслеживать, подавить с записанной причиной или принять. Цель — гейт, где красная сборка всегда означает «сделай вот это конкретное», а не «иди подтверди 40 вещей, с которыми ничего не сделать».

Почему это работает

Зачем сканировать реестр непрерывно, а не только на сборке? Потому что пересечение между твоей ведомостью материалов и базой CVE двигается под тобой. Образ, чисто отсканированный в понедельник, может иметь три новые критические в четверг — не потому что образ изменился, а потому что изменилась база, когда опубликовали новый CVE против пакета, который ты уже отгрузил. Знаменитый пример — Log4Shell: миллионы артефактов за ночь перешли из «чистых» в «критический RCE» при нулевом изменении кода. Сканирование на сборке отвечает на «чисто ли сейчас?»; сканирование реестра/рантайма отвечает на «чисто ли ещё то, что мы запускаем?» — и только второе ловит CVE, раскрытый после деплоя.

Резать вход, а не только фильтровать выход

Самый дешёвый способ выиграть игру в триаж — иметь меньше того, что триажить. Два структурных хода сжимают вход ещё до всякой фильтрации:

  • Минимальные базовые образы. Образ node:20 весит ~1,1 ГБ и тащит весь userland Debian — shell, apt, десятки библиотек, которых твоё приложение не касается, каждая — потенциальный CVE. Образ distroless или alpine несёт только рантайм. Замена node:20 на gcr.io/distroless/nodejs20 рутинно роняет находки слоя ОС со ~150 до единиц — потому что ты удалил пакеты, а не подавил их CVE. Меньше пакетов — единственный фикс, удаляющий сам уязвимый код, а не спорящий о его достижимости.
  • Пинить и обновлять осознанно. Lock-файлы делают сканы воспроизводимыми (ты сканируешь ровно то, что отгружается), а ровный темп обновления зависимостей (Dependabot/Renovate) означает, что ты применяешь маленькие патчи непрерывно, а не упираешься в обрыв на 200 CVE при следующем вынужденном мажорном апгрейде.
Выбери лучший вариант

Скан CI сообщает 312 CVE (41 критическая) на только что включённом пайплайне. Выбери ответ, который сеньор отгрузит первым.

Викторина

Образ, прошедший скан чисто в понедельник, в четверг показывает 3 новые критические без изменения кода или зависимостей. Что произошло?

Викторина

CVSS 9.8 RCE найден в dev-only build-инструменте, вырезанном tree-shaking из продакшен-бандла. Должен ли он ронять продакшен-гейт деплоя?

Расставь шаги по порядку

Упорядочь воронку триажа от сырого вывода сканера до отгружаемого решения:

  1. 1 Сканер сопоставляет твою ведомость материалов с базой CVE → сырые находки
  2. 2 Отбрось находки, чей уязвимый код недостижим от точки входа
  3. 3 Отдели исправимые находки от won't-fix / патча-ещё-нет
  4. 4 Ранжируй исправимое+достижимое множество по серьёзности в контексте развёртывания
  5. 5 Гейти сборку на этом ранжированном множестве; остальное отслеживай/подавляй с причиной
Вспомните перед уходом
  1. 01
    Объясни, как SCA/image-сканер порождает свои находки и почему свежий образ node:20 сообщает о десятках CVE в первый день.
  2. 02
    Почему «ронять сборку на любой CRITICAL» — худший гейт, чем «ронять только на исправимом + high/critical + неподавленном», и как сюда вписываются достижимость и сканирование реестра?
  3. 03
    Ты наследуешь пайплайн с --severity HIGH,CRITICAL --exit-code 0 (скан сообщает, но никогда не роняет) и ~300 стоящими находками. Какова твоя последовательность, чтобы вернуть гейту смысл, не останавливая поставку?
Итог

Контейнер — это две цепочки поставок: базовый образ ОС и зависимости твоего приложения, — и SCA/image-сканер инвентаризирует обе, затем сопоставляет эту ведомость материалов с базой CVE. Поэтому находить уязвимости бесплатно и шумно: CVE означает, что уязвимая версия присутствует, а не что её код достижим, эксплуатируем или хотя бы исправим. Ход сеньора — воронка триажа: отбросить недостижимые находки, отделить исправимое от won’t-fix, ранжировать остальное по эксплуатируемости в твоём развёртывании и гейтить сборку только на исправимом + high/critical + неподавленном, чтобы красная сборка всегда означала конкретное действие — а не «иди подтверди 40 вещей, которые не починить». Сначала сожми вход минимальным базовым образом и непрерывными обновлениями и сканируй реестр непрерывно, потому что база CVE двигается под тобой (Log4Shell перевернул миллионы неизменных артефактов за ночь). Рефлекс, который надо убить, — --exit-code 0: тихо обезвреженный сканер хуже отсутствия, потому что позволяет верить, что ты защищён.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 6 завершено
Связанные уроки
углубляется в

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.