MAATRIX / Блог / Trivy находит не всё: где заканчивается зона ответственности сканера

Trivy находит не всё: где заканчивается зона ответственности сканера

MAATRIX

Поставили Trivy в CI, он зелёный — значит, образ безопасен? Нет, и это не придирка к формулировке: сканер находит только то, что уже известно и описано в базе данных, а всё остальное — включая большинство реальных инцидентов последних лет — проходит мимо него незамеченным. Разберём честно, где Trivy действительно закрывает вопрос, а где он создаёт лишь иллюзию контроля.

Что Trivy делает хорошо

Trivy — сканер от Aqua Security, который проверяет Docker-образы, файловые системы, git-репозитории и Kubernetes-манифесты на известные уязвимости (CVE) в пакетах и на утечки секретов. Его сильная сторона — именно это: сопоставление того, что установлено в образе, с базами данных известных уязвимостей.

Типичный запуск:

trivy image nginx:1.25

Trivy разворачивает слои образа, определяет ОС-дистрибутив и версии установленных пакетов (через dpkg, rpm, apk в зависимости от базового образа), затем сверяет их с несколькими источниками: NVD (National Vulnerability Database), GitHub Security Advisories, Alpine SecDB, Debian Security Tracker и другими — список зависит от экосистемы. Для приложений он идёт дальше и разбирает файлы зависимостей: package-lock.json, go.sum, requirements.txt, Gemfile.lock, pom.xml — и проверяет версии библиотек по тем же базам.

Результат — таблица с CVE-идентификатором, уровнем критичности (CRITICAL/HIGH/MEDIUM/LOW), затронутым пакетом, установленной версией и версией с исправлением:

nginx:1.25 (debian 12.2)
========================
Total: 3 (HIGH: 2, CRITICAL: 1)

┌─────────────┬───────────────┬──────────┬────────┬───────────────┬───────────────┐
│   Library    │ Vulnerability │ Severity │ Status │   Installed   │     Fixed     │
├─────────────┼───────────────┼──────────┼────────┼───────────────┼───────────────┤
│ libssl3     │ CVE-2024-XXXX │ CRITICAL │ fixed  │ 3.0.11-1      │ 3.0.13-1      │
└─────────────┴───────────────┴──────────┴────────┴───────────────┴───────────────┘

Это ровно та задача, для которой сканер и создан: если в образе стоит libssl версии, для которой уже есть публично известная уязвимость и патч, Trivy об этом скажет надёжно и быстро. Такая проверка ловит самый частый и самый банальный источник компрометации — образ, собранный полгода назад и с тех пор не пересобиравшийся, с десятком известных дыр внутри. Если у вас ещё нет привычки гонять сканер по образам вообще, начать стоит с подборки инструментов для проверки сервера на уязвимости — Trivy там один из вариантов, но не единственный.

Отдельно Trivy умеет искать секреты, случайно закоммиченные в образ или репозиторий — API-ключи, приватные ключи, токены доступа по регулярным выражениям и эвристикам:

trivy fs --scanners secret /path/to/repo

И проверять IaC-конфигурации — Terraform, CloudFormation, Kubernetes-манифесты, Dockerfile — на нарушение общепринятых практик безопасности: контейнер без ограничения ресурсов, S3-бакет без шифрования, под без securityContext.

trivy config ./terraform/

База данных — это прошлое, а не настоящее

Ключевое ограничение Trivy вытекает прямо из принципа его работы: он сравнивает установленное ПО со списком уже известных уязвимостей. Уязвимость попадает в базу после того, как её кто-то нашёл, ответственно раскрыл (или она утекла), ей присвоили CVE-номер, и это отразили в NVD или другом источнике. Весь этот цикл занимает от нескольких дней до нескольких месяцев.

0-day уязвимость — та, о которой ещё никто публично не знает (или знает, но не сообщил) — по определению отсутствует в базе Trivy. Сканер её не увидит, потому что нечего сверять: библиотека numerически та же, что и вчера, просто про её баг ещё никто не написал. Это не недоработка Trivy, это фундаментальное свойство любого сканера баз данных известных уязвимостей — то же самое верно для Grype, Snyk, Clair и любого другого инструмента этого класса.

Есть и обратная сторона задержки: CVE может существовать неделями, прежде чем появится запись в базе, которую использует конкретная версия Trivy. Если вы сканировали образ во вторник и получили ноль критичных находок, это означает «ноль известных на момент сканирования уязвимостей», а не «уязвимостей нет». Через неделю тот же образ, без единого изменения в нём самом, может показать три CRITICAL — потому что база обновилась, а не потому что образ стал хуже. Отсюда практический вывод: разовое сканирование перед релизом — плохая практика, сканировать нужно регулярно (в идеале — ежедневно через cron или отдельный CI-джоб по расписанию, а не только при сборке).

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Логика приложения — не территория Trivy

Trivy анализирует состав образа: какие пакеты установлены, какие версии, есть ли для них известные CVE. Он ничего не знает о том, что делает ваш код. Это принципиально другая задача — статический анализ поведения приложения (SAST) или анализ бизнес-логики, и Trivy для неё не предназначен и не пытается быть.

Практический пример: если в вашем API есть эндпоинт /api/orders/{id}, который отдаёт заказ по числовому ID без проверки, что этот заказ принадлежит текущему пользователю (классический IDOR — Insecure Direct Object Reference), Trivy пройдёт мимо этого полностью. Все библиотеки в образе актуальны, версия фреймворка свежая, CVE нет — а дыра в авторизации при этом реальна и эксплуатируема любым, кто догадается подставить чужой ID в URL.

Та же история с:

  • неправильной проверкой прав доступа (пользователь роли viewer может дёрнуть ручку, доступную только admin, потому что проверка роли забыта на одном из эндпоинтов);
  • логическими багами в бизнес-процессах (промокод можно применить повторно из-за отсутствия проверки на стороне сервера, цена товара пересчитывается на клиенте и принимается сервером как есть);
  • SQL-инъекциями или XSS в собственном коде, если он не использует уязвимую версию библиотеки, а сам неправильно формирует запросы или не экранирует вывод;
  • небезопасной конфигурацией приложения, которая не описывается как «неправильный IaC-файл» — например, слишком долгий срок жизни JWT-токена, зашитый прямо в код секрет для подписи сессий, или отладочный роут, забытый в проде.

Всё это — задачи для code review, SAST-инструментов (например, Semgrep или CodeQL для анализа кода), тестирования на проникновение (пентеста) или как минимум вдумчивого чтения собственного кода с точки зрения «а что если этот параметр придёт не таким, каким я ожидаю». Trivy не подменяет ни одно из этого — он занимает свою узкую нишу и хорошо её закрывает, но эта ниша уже, чем «безопасность приложения» целиком.

Пример: зелёный Trivy и дыра в авторизации одновременно

Возьмём реалистичный сценарий. Есть небольшое SaaS-приложение на Node.js, упакованное в Docker-образ на базе node:20-slim. CI-пайплайн настроен честно: перед деплоем запускается

trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:latest

и деплой блокируется, если находятся критичные уязвимости. Команда обновляет зависимости раз в пару недель через npm audit fix и Dependabot, образ пересобирается регулярно. Формально — зелёная зона: Trivy молчит, npm audit тоже не ругается, базовый образ свежий.

При этом в коде эндпоинта смены пароля забыли проверку текущего пароля — форма принимает только новый пароль и user_id из тела запроса, а не из токена сессии:

app.post('/api/change-password', async (req, res) => {
  const { user_id, new_password } = req.body;
  await db.users.update({ id: user_id }, { password: hash(new_password) });
  res.json({ ok: true });
});

Любой авторизованный пользователь может сменить пароль любому другому, просто подставив чужой user_id в запрос. Это критичная уязвимость — полный захват любого аккаунта — но она не в зависимости, не в базовом образе, не в конфигурации, а в четырёх строчках прикладного кода. Trivy её не найдёт никогда, потому что она не туда смотрит: сканер проверяет *что установлено*, а не *что делает написанный вами код*.

Вывод не в том, что Trivy бесполезен — он честно закрыл бы реальный класс проблем, если бы они там были. Вывод в том, что «сканер зелёный» и «приложение безопасно» — это два разных утверждения, и разрыв между ними — самое частое заблуждение, с которым сталкиваются команды, которые только начинают выстраивать процесс безопасности.

Как встроить Trivy в пайплайн правильно

Чтобы Trivy приносил максимум пользы в рамках своей зоны ответственности, полезны несколько практик.

Сканируйте на каждой сборке и по расписанию отдельно. Джоб в CI ловит новые уязвимости, попавшие в свежедобавленные зависимости, а отдельный ежедневный/еженедельный запуск по уже задеплоенным образам ловит те CVE, что появились в базе данных already после релиза:

# в CI при сборке
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed myapp:$CI_COMMIT_SHA

# по расписанию (cron) для образов в проде
trivy image --severity CRITICAL,HIGH registry.example.com/myapp:latest

Флаг --ignore-unfixed стоит обсудить отдельно: он скрывает уязвимости, для которых ещё нет патча. Это удобно, чтобы не блокировать деплой из-за того, что вы всё равно не можете исправить прямо сейчас, но такие находки не стоит просто игнорировать — держите их на отдельном дашборде и возвращайтесь к ним, когда патч появится.

Кэшируйте базу уязвимостей в CI (Trivy умеет сохранять .cache/trivy между джобами) — иначе каждый прогон тратит время и трафик на скачивание одной и той же базы.

Заведите список исключений с обоснованием, а не просто подавляйте находки молча:

# .trivyignore
CVE-2023-XXXXX # ложное срабатывание, пакет не используется в рантайме, тикет INFRA-142

Не полагайтесь только на severity-метку из общей базы. CRITICAL для библиотеки, которая используется на веб-фронте с прямым доступом из интернета, — это не то же самое, что CRITICAL для той же библиотеки, используемой во внутреннем batch-скрипте без сетевого доступа. Приоритизируйте по реальной эксплуатируемости в вашем контексте, а не только по цифре из таблицы.

И главное — воспринимайте Trivy как один слой из нескольких, а не как финальную проверку. Рядом должны быть: code review с фокусом на авторизацию и бизнес-логику, статический анализ кода (SAST), а на более зрелом уровне — периодический пентест или хотя бы самостоятельный разбор ключевых сценариев «что если пользователь подставит не тот ID/токен/параметр».

Куда Trivy не дотягивается даже в своей нише

Кроме концептуальных ограничений (0-day, логика приложения), у Trivy есть и более приземлённые слепые зоны внутри его собственной задачи — про них тоже стоит знать, чтобы не удивляться.

Приватные зависимости и внутренние пакеты, не опубликованные в публичных реестрах, Trivy проверить не может — у него просто нет базы для них. Если у вас есть внутренняя библиотека с уязвимостью, известной только внутри компании, отследить её нужно вручную или через собственный трекер.

Компилируемые бинарники без manifest-файла (например, статически собранный Go-бинарник, скопированный в scratch-образ без go.sum рядом) Trivy может просканировать по встроенным метаданным сборки, но это работает не всегда надёжно — зависит от того, как именно собран бинарник и какая версия Trivy используется.

Уязвимости в конфигурации на уровне рантайма, которые не выражены явно в IaC-файле — например, переменная окружения с секретом, переданная через docker run -e, а не описанная в файле, который сканирует trivy config, — тоже вне поля зрения.

И отдельный практический нюанс: если вы одновременно проверяете сервер антивирусным сканером — например, ClamAV, — не путайте задачи. ClamAV ищет сигнатуры известного вредоносного кода в файлах (условно — "этот файл совпадает с образцом вируса X"), а Trivy ищет уязвимые версии легитимного, незловредного ПО. Это два разных класса инструментов, которые решают разные проблемы и совершенно не заменяют друг друга — использование одного не снимает необходимости в другом, если оба сценария у вас актуальны.

Инфраструктурный слой: то, что можно контролировать помимо сканера

Раз Trivy закрывает только часть картины, разумно опереться и на другие слои защиты, которые снижают ущерб от того, что сканер пропустил. На уровне инфраструктуры это в первую очередь принцип наименьших привилегий: контейнер приложения не должен работать от root, не должен иметь доступа к Docker-сокету хоста и не должен иметь сетевого доступа туда, где он не нужен — подробнее о том, зачем и как разграничивать сервисы, в статье про изоляцию сервисов через Docker.

Отдельно стоит проверять образы вручную перед первым использованием, особенно если это не официальный образ, а что-то из Docker Hub от малоизвестного автора — как минимум посмотреть слои и репутацию источника, это описано в статье про проверку чужого Docker-образа до запуска. Trivy тут тоже участвует, но как один из шагов проверки, а не единственный.

Если инфраструктура описана через Terraform или Ansible, а не руками через SSH, у вас автоматически появляется аудируемая история изменений и меньше шансов на «настроили один раз и забыли» — конфигурация из голов разработчиков переезжает в код, который можно ревьюить. И на уровне процесса полезно свести базовые проверки безопасности сервера в чек-лист, который проходится не «когда вспомнили», а на каждом новом окружении.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Если Trivy ничего не находит, значит ли это, что образ безопасен?

Нет. Это означает, что в образе нет пакетов с уязвимостями, известными на момент сканирования и присутствующими в используемых базах данных. Уязвимости в собственном коде приложения, логические ошибки авторизации и ещё не раскрытые (0-day) проблемы Trivy не видит в принципе — это вне его задачи.

Нужен ли SAST-инструмент, если уже используется Trivy?

Да, если в проекте есть заметный объём собственного кода. Trivy проверяет состав образа (какие пакеты установлены), а SAST-инструменты вроде Semgrep или CodeQL анализируют, что делает написанный вами код — это разные слои проверки, и они дополняют друг друга, а не дублируют.

Как часто нужно сканировать образы Trivy?

Минимум — при каждой сборке в CI. Отдельно стоит настроить регулярное сканирование уже задеплоенных образов по расписанию (например, раз в сутки), потому что базы данных уязвимостей обновляются постоянно, и образ, безопасный вчера, может показать новые находки сегодня без единого изменения в самом образе.

Можно ли доверять только цифре severity (CRITICAL/HIGH) при принятии решения о блокировке деплоя?

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

Trivy и ClamAV — это конкурирующие инструменты, нужно выбрать один?

Нет, это инструменты для разных задач. ClamAV ищет в файлах сигнатуры уже известного вредоносного кода (вирусы, трояны, веб-шеллы), Trivy ищет уязвимые версии легитимных пакетов и библиотек по базам CVE. Если у вас есть сценарии, где актуальны оба риска — например, сервер принимает файлы от пользователей и одновременно собирает Docker-образы, — имеет смысл использовать оба инструмента, каждый в своей роли.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →