Как защитить веб-приложение, которое вы не до конца понимаете
Где-то есть веб-приложение, за которое вы отвечаете, и никто не может, не отводя глаз, сказать вам, безопасно ли выставлять его форму входа в интернет. Может, приложение досталось вам по наследству. Может, коллега «наваял» его за выходные. Может, ему десять лет, а исходной команды давно нет. Всё это одна и та же задача: вам нужно защитить веб-приложение, внутренности которого вы не до конца понимаете. Вы не можете пройтись по каждому маршруту, каждой проверке авторизации, каждой зависимости и каждому обращению к базе и лично за них поручиться. Переписывание требует кварталов, которых у вас нет, а оставить всё открытым неприемлемо. Об этой середине и статья.
Если полный аудит сразу невозможен, несколько мер помогут снизить риски, пока вы изучаете приложение. Начните с публичных точек входа, чувствительных данных и прав доступа. Статья рассматривает три взаимодополняющие меры:
| Слой | Что делает |
|---|---|
| Сканирование секретов | Находит учётные данные, утёкшие в репозиторий и его историю. |
| Фильтрация на периметре | WAF перед приложением, фильтрующий явно злонамеренный трафик. |
| Защита от ботов | CAPTCHA или обнаружение ботов по оценке на формах, которые должны оставаться публичными. |
Поиск секретов проверяет репозиторий и историю. WAF фильтрует трафик до приложения. Защита от ботов сдерживает автоматизированные злоупотребления. Это разные уровни, и они не доказывают безопасность самого приложения.
Виртуальная заплатка блокирует эксплуатацию конкретной уязвимости без изменения кода, например адресным правилом WAF. Это одно применение фильтрации, а не название всех мер статьи.
Периметру статья уделяет большую часть времени, но мы также затронем немного того, что можно сделать за периметром — посмотрев вместо этого внутрь: на библиотеки, которые приложение реально запускает, на его исходный код и на его поведение в рантайме. Сканирование зависимостей находит библиотеки с известными уязвимостями, которые приложение уже тащит с собой, а SAST и DAST в конце статьи ловят шаблоны на уровне кода, которых не видит никакой периметровый слой.
У большинства этих слоёв есть надёжный самостоятельно разворачиваемый вариант, работающий на любом хостинге: gitleaks для секретов, ModSecurity для WAF, OSV-Scanner для зависимостей, Semgrep для SAST, ZAP для DAST. Они остаются основными на протяжении всей статьи. Что касается управляемых облачных эквивалентов, где вы обмениваете часть работы по настройке на настроенный вендором набор правил и его данные об угрозах, — их предлагает каждое крупное облако. В качестве конкретного ориентира, когда управляемый пример помогает, мы будем брать Google Cloud Platform: Cloud Armor для WAF, Artifact Analysis для сканирования контейнеров, reCAPTCHA Enterprise и Adaptive Protection для защиты от ботов. Выбор одного облака делает эти примеры конкретными, а не отмашкой; у AWS, Azure и Cloudflare есть эквиваленты, которые мы упомянем коротко, но разбирать глубоко не будем.
Три категории неизвестного
Прежде чем брать инструменты, стоит назвать, от чего вы на самом деле защищаетесь, потому что от этого зависит, какой слой важнее всего. Неизвестное распадается на три группы.
Что в коде и в репозитории. Вы не знаете, какие маршруты существуют, — легаси-приложения растут органически, и там наверняка есть админские эндпоинты, о которых никто не упоминал. Вы не знаете и того, что код на самом деле делает, — либо потому что не успеваете его прочитать (легаси), либо потому что его изначально никто внимательно не читал (сгенерирован ИИ). И вы не знаете, что уже утекло в историю git. DAST зондирует работающее приложение снаружи, чтобы составить карту того, что реально доступно; SAST даёт неполную, но полезную карту рискованных шаблонов кода; сканирование секретов ловит учётные данные, забытые в старых коммитах.
Зависимости. Старые или неподдерживаемые библиотеки могут иметь известные уязвимости. Инвентаризация и сканер выявляют затронутые версии, но находки нужно оценивать с учётом реальной доступности приложения для атаки.
Рабочий трафик. Журналы запросов и WAF показывают наблюдаемые атаки и ложные срабатывания. Это частичная видимость, а не полный учёт атакующих или доказательство безвредности неотмеченного трафика.
Ни один инструмент не решает всё это. Каждый из трёх слоёв ниже адресует один или несколько пунктов; сканирование зависимостей (о нём после слоёв) и SAST/DAST (в конце) наращивают глубину, когда основа уже на месте.
Сканирование секретов
Учётные данные попадают в код, конфигурацию, тестовые данные и документацию. Удаление значения из текущего файла не удаляет его из прежних коммитов, поэтому проверяйте и историю, и рабочее дерево.
Проверьте нужную историю, разберите находки и отзовите или замените действительно раскрытые учётные данные. Добавьте проверки новых изменений в CI.
Инструменты
- gitleaks — быстрый, на Go, с отличными правилами по умолчанию, покрывающими большинство провайдеров (AWS, GCP, Azure, Stripe, Twilio, токены GitHub, приватные ключи, JWT). Запускайте по полной истории, а не только по HEAD.
- trufflehog — похожее обнаружение, но добавляет шаг проверки: он тестирует, действительно ли найденный секрет живой (например, делает вызов
sts:GetCallerIdentityдля ключей AWS). Вывод с более высоким сигналом, дополнительная задержка того стоит. - git-secrets — ориентирован на AWS, легковесный, полезен как pre-commit-хук.
- GitHub Secret Scanning — бесплатно для публичных репозиториев, Advanced Security для приватных. Провайдеры могут отзывать обнаруженные токены; подтвердите отзыв, не полагаясь на автоматику.
Запускаем сканирование и читаем вывод
gitleaks можно установить несколькими способами — готовый бинарник, Homebrew, go install или через Docker. Здесь мы возьмём Docker для удобства: на хост ничего ставить не надо, одна и та же команда работает на любой машине с демоном Docker, а раннеры CI получают тот же вызов, что и ваш ноутбук.
Я только что прогнал это по репозиторию, в котором живёт эта статья:
docker run --rm -v "$(pwd):/repo" zricethezav/gitleaks:latest \
git /repo --log-opts="--all" --redact --verbose--log-opts="--all" проходит по каждому коммиту каждой ветки, а не только по HEAD; --redact маскирует значение секрета в выводе, чтобы сам отчёт не стал новой утечкой; --verbose печатает полный блок по каждой находке вместо просто сводного счётчика.
gitleaks выдаёт по одной находке на каждую потенциальную строку-секрет, которую опознаёт — один и тот же набор полей для любого совпадения, независимо от того, какое правило сработало. Типичная находка выглядит так:
Finding: STRIPE_SECRET_KEY=REDACTED
Secret: REDACTED
RuleID: stripe-access-token
Entropy: 4.175736
File: blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx
Line: 225
Commit: 49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa
Fingerprint: 49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:stripe-access-token:225Пять полей, которые имеют значение:
| Поле | О чём говорит |
|---|---|
| RuleID | Какое правило обнаружения сработало (stripe-access-token, aws-access-token, generic-api-key, private-key и т. д.). Полный набор правил по умолчанию лежит в config/gitleaks.toml в репозитории gitleaks — каждое правило со своей регуляркой, описанием и любыми фильтрами по ключевым словам. |
| File + Line | Где совпадение. |
| Commit | Какой коммит его внёс — не обязательно самый последний. gitleaks находит его там, где он впервые появляется в истории. |
| Entropy | Энтропия Шеннона совпавшей строки, в битах на символ. Выше — значит выглядит более случайно, а это обычно значит, что вероятнее это настоящий секрет. Правила для общих ключей API используют энтропию как основной сигнал; правила под конкретных провайдеров (Stripe, AWS) совпадают по префиксу и в ней не нуждаются. |
| Fingerprint | Устойчивый идентификатор, которым вы подавите эту находку, если она окажется допустимой. Об этом ниже. |
Что энтропия на самом деле измеряет
Энтропия частот символов, используемая при поиске секретов, равна −Σ p(c) log₂ p(c). Максимум — log₂(N) для N равновероятных символов. Порядок не учитывается, поэтому предсказуемая последовательность может получить высокий балл; это не доказательство криптографической случайности.
Энтропия — эвристика вместе с шаблонами и контекстом. Теоретический потолок — 4 бита на символ для hex, около 5,95 для 62 букв и цифр и 6 для 64 символов Base64. Короткие образцы часто дают меньшие значения.
Оценка 4,175736 — один сигнал, а не доказательство действующего ключа. Перед решением проверьте правило и происхождение значения.
Разбор находок
Когда вы реально прогоните gitleaks по настоящему репозиторию, находки будут трёх категорий — и одна и та же реакция не подходит всем трём:
- Настоящие учётные данные: отзовите или замените их, исследуйте утечку и удалите из активных файлов. Перезапись истории может уменьшить дальнейшее раскрытие, но не отзывает ключ.
- Намеренный пример: до исключения подтвердите, что это несекретная заглушка или одобренный отозванный образец. Цитирование в статье по безопасности не доказывает безвредность.
- Ложное срабатывание: запишите причину и исключите конкретную находку.
Для категорий 2 и 3 лечение — файл .gitleaksignore в корне репозитория со списком идентификаторов (поле Fingerprint из таблицы выше) тех находок, которые вы просмотрели и одобрили:
# Намеренно: секреты, приведённые как доказательство в разборе атаки на цепочку поставок
49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:stripe-access-token:225
49e4ca23a5ee6d1ef6b1a566a580a4086fbb84aa:blog/src/content/blog/anatomy-of-a-developer-targeted-supply-chain-attack.mdx:generic-api-key:193gitleaks будет пропускать именно эти находки при последующих сканированиях. Комментируйте щедро — будущему вам нужно знать, была ли каждая запись одобрена потому, что она намеренная, или потому, что это ложное срабатывание, и комментарий — единственная долговечная запись об этом решении.
Остановить следующую утечку в момент коммита
После проверки истории и документирования исключений добавьте gitleaks в:
- CI: сканируйте коммиты до слияния и публикации.
- Хуки pre-commit: проверяйте подготовленные изменения до создания коммита. Это уменьшает случайные утечки, но проверки могут пропускать неизвестные шаблоны или обходиться.
Фильтрация на периметре с WAF
Web Application Firewall проверяет HTTP(S)-запросы по набору правил до того, как они попадут в приложение. Он может разрешать, регистрировать или блокировать запросы, а также требовать дополнительную проверку. WAF полезен для:
- Перехвата очевидных полезных нагрузок для классов атак: SQL-инъекция, XSS, локальное/удалённое включение файлов, инъекция команд, обход путей.
- Блокировки известных плохих путей (
/wp-admin/,.git/config,.env). - Ограничения частоты, блокировки по репутации IP, гео-фильтрации.
- Покупки времени, когда в зависимости, которую вы не можете обновить сегодня, выходит CVE.
Он бесполезен против сломанной бизнес-логики, сломанной авторизации или плохого проектирования сессий. Это компенсирующая мера, а не лечение.
Управляемые и самостоятельные WAF различаются движками, правилами, лимитами и эксплуатационными возможностями. Распространённый свой стек — ModSecurity с OWASP Core Rule Set. CRS объединяет совпадения через оценку аномалий; управляемые продукты могут сочетать производные CRS и собственные детекторы.
Управляемый: Cloud Armor
Управляемый WAF уменьшает объём обслуживаемой инфраструктуры, но всё равно требует интеграции, настройки и анализа ложных срабатываний. Выбор зависит от точки входа трафика и нужных мер.
Родной для GCP вариант — Google Cloud Armor. Он подключается к HTTP(S)-балансировщику GCP, а его предварительно настроенные группы правил (sqli-v33-stable, xss-v33-stable, lfi-v33-stable, rce-v33-stable и т. д.) выведены напрямую из OWASP CRS — того же набора правил, который вы установили бы руками с ModSecurity.
Настраивать Cloud Armor можно через консоль GCP, CLI gcloud, REST API или — как мы покажем ниже — декларативно в Terraform. Модель одна и та же независимо от интерфейса: политика безопасности Cloud Armor — это список правил с условиями совпадения и действиями (allow, deny(403), rate_based_ban). Минимальная политика, включающая предварительно настроенные группы правил CRS и разумное действие по умолчанию, выглядит так:
resource "google_compute_security_policy" "app" {
name = "app-waf"
rule {
action = "deny(403)"
preview = true
priority = 1000
match {
expr { expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 2})" }
}
description = "Block SQL injection"
}
rule {
action = "deny(403)"
preview = true
priority = 1001
match {
expr { expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 2})" }
}
description = "Block XSS"
}
rule {
action = "allow"
priority = 2147483647
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Default allow"
}
}Предварительный режим оценивает правило без блокировки. Настройте журналирование и выборку, затем проверьте репрезентативный трафик до включения блокировки. Отсутствие записей в выборочном журнале не доказывает отсутствие ложных срабатываний.
Пользовательские правила Cloud Armor используют подмножество CEL, поэтому правила ModSecurity SecRule нужно переписывать. Можно выбрать одну из версий CRS, поддерживаемых Google, но нельзя загрузить произвольный релиз проекта. Учитывайте плату за политики и запросы, а также расходы на балансировщик.
Другие варианты — AWS WAF, Azure WAF и Cloudflare WAF. Точки подключения, наборы правил и режимы предварительной проверки или журналирования различаются.
Самостоятельно: ModSecurity + NGINX
Самостоятельный путь — правильный выбор, когда вам нужен полный контроль над пользовательскими правилами, вы не в облаке с хорошим управляемым предложением, стоимость за запрос важна при ваших объёмах или ограничения комплаенса делают устройство, которым вы управляете сами, проще для обоснования, чем сервис вендора.
Коннектор NGINX загружается как динамический модуль и связывает NGINX с libmodsecurity, которая проверяет настроенные правила. Архитектура выглядит так:
Интернет → NGINX + ModSecurity → приложение выше по потокуВот как расставлены три части:
- NGINX — обратный прокси. Он терминирует TLS, принимает входящие HTTP-запросы и — когда их ничто не блокирует — проксирует их приложению выше по потоку на приватный порт.
- libmodsecurity — движок вычисления правил. Это библиотека на C++, отдельная от самого NGINX, без собственного сетевого кода: она просто принимает запрос на вход и возвращает вердикт.
- Коннектор ModSecurity для NGINX — динамический модуль (файл
.so), который связывает эти два. Он встраивается в конвейер обработки запросов NGINX и для каждого входящего запроса передаёт URL, заголовки и тело в libmodsecurity на вычисление.
Все три части работают внутри одного процесса NGINX. Механизм — загрузчик динамических модулей NGINX: и коннектор, и libmodsecurity в итоге загружаются в каждый рабочий процесс NGINX при старте, без отдельного демона ModSecurity, без IPC, без сокета между ними.
Коннектор вызывает libmodsecurity внутри рабочего процесса NGINX. Это исключает дополнительный сетевой переход, но проверка правил всё равно требует работы. Собирайте коннектор под используемые версию NGINX и конфигурацию модулей.
Для каждого запроса NGINX передаёт URL, заголовки и тело в libmodsecurity (через коннектор). libmodsecurity прогоняет их через загруженные правила — OWASP CRS плюс любые пользовательские, — накапливает оценку аномальности и возвращает действие: allow, log или deny. Если вердикт deny, NGINX возвращает настроенный ответ об ошибке (обычно 403) и вверх по потоку не пересылает. Иначе запрос идёт как обычно.
Когда WAF на месте, убедитесь, что всё это верно:
- Ограничьте доступ к приложению так, чтобы запросы проходили через WAF. Общедоступный исходный сервер позволяет обойти защиту.
- TLS терминируется на WAF. ModSecurity не может проверить то, что не может расшифровать.
- WAF теперь единая точка отказа — запустите хотя бы два экземпляра за балансировщиком, если аптайм важен.
- Объём журнала аудита подскочит. Планируйте хранилище.
Пример установки на Debian/Ubuntu (сокращённо):
# libmodsecurity
git clone --depth 1 -b v3/master https://github.com/owasp-modsecurity/ModSecurity
cd ModSecurity && git submodule init && git submodule update
./build.sh && ./configure && make -j$(nproc) && make install
# модуль-коннектор для NGINX (версия должна совпадать с вашей версией NGINX)
git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx
cd nginx-${NGINX_VERSION}
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules && cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/
# OWASP Core Rule Set
git clone --depth 1 https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs
cp /etc/nginx/modsec/crs/crs-setup.conf.example /etc/nginx/modsec/crs/crs-setup.confЗатем в nginx.conf:
load_module modules/ngx_http_modsecurity_module.so;
http {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server {
listen 443 ssl;
server_name legacy-app.example.com;
ssl_certificate /etc/ssl/certs/app.crt;
ssl_certificate_key /etc/ssl/private/app.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}Сначала выкатывайте в режиме только обнаружения. Поставьте SecRuleEngine DetectionOnly хотя бы на неделю. Читайте журнал аудита. Вы ищете ложные срабатывания (правила CRS, срабатывающие на легитимном трафике — редакторы, админ-панели, загрузки файлов) и неожиданные истинные срабатывания (атаки, которые уже идут по вам). CRS использует оценку аномальности — правила добавляют к счёту, и запрос блокируется только когда сумма пересекает порог, — что делает настройку мягче, чем блокировка по каждому правилу.
Когда найдёте ложное срабатывание, сначала используйте SecRuleRemoveById с областью до конкретного location, во вторую очередь понижайте уровень паранойи на пути, и лишь в крайнем случае отключайте правило глобально. Только после того, как логи станут достаточно тихими, чтобы быть осмысленными, переключайте SecRuleEngine On.
Для /api/report?format=X это правило фазы 1 отклоняет значения format вне разрешённого списка в строке запроса. Оно не требует наличия параметра и не проверяет его в теле запроса:
SecRule REQUEST_URI "@beginsWith /api/report" \
"id:1000001,phase:1,chain,deny,status:400,\
msg:'virtual patch: /api/report format allowlist'"
SecRule ARGS:format "!@rx ^(pdf|csv|json)$"Защита от ботов и CAPTCHA
Остальные слои этого набора почти не касаются класса угроз, который почти наверняка настигнет публичное легаси-приложение: оппортунистические злоупотребления ботами на эндпоинтах, которые обязаны быть неаутентифицированными — спам-регистрации, прогоны подстановки украденных учётных данных по вашей форме входа, массовый скрапинг, автоматизированные злоупотребления купонами, потоки сбросов пароля, вызывающие рассылку писем в масштабе.
Публичным входу, регистрации и сбросу пароля нужны меры против злоупотреблений наряду с аутентификацией. Лимиты и сигналы ботов помогают, но не идеально отличают распределённую атаку от обычного трафика.
Варианты ниже охватывают браузерные проверки, оценку риска запросов и обнаружение аномалий трафика. Это связанные, но разные возможности.
Браузерные интеграции создают токены для проверки на сервере или периметре. Сетевые детекторы используют другие сигналы; некоторые продукты дополняют их клиентским JavaScript.
Начнём с самого простого в настройке — Cloudflare Turnstile — а затем посмотрим на более глубокую родную для GCP комбинацию reCAPTCHA Enterprise плюс Cloud Armor Adaptive Protection для команд, которым нужно больше.
Cloudflare Turnstile (самый простой путь)
Cloudflare Turnstile предоставляет браузерные проверки и токен для Siteverify API. Управляемый режим может потребовать взаимодействия; он не всегда невидим и не выдаёт оценку риска как reCAPTCHA. Миграция с reCAPTCHA требует изменений клиента и сервера.
Turnstile можно использовать без переноса хостинга или DNS в Cloudflare. Настройте и виджет, и серверную проверку по документации.
Turnstile перестаёт быть достаточным, когда вам нужен более тонкий контроль — оценки по каждому запросу, на которые можно реагировать (а не просто пройдено/не пройдено), применение на балансировщике вместо кода приложения или правила тоньше, чем «вызов пройден / не пройден». Именно этот пробел закрывает комбинация reCAPTCHA Enterprise + Cloud Armor ниже.
reCAPTCHA Enterprise (более глубокая интеграция с GCP)
reCAPTCHA может возвращать оценки риска и причины. Балл — сигнал риска, а не калиброванная вероятность того, что посетитель человек. Типы ключей и поведение проверок зависят от интеграции.
Cloud Armor может оценивать поддерживаемые токены reCAPTCHA в правилах безопасности. Проверяйте действительность токена вместе с баллом; для отсутствующих и ошибочных токенов нужна явная политика.
rule {
action = "deny(403)"
priority = 500
match {
expr {
expression = "token.recaptcha_action_token.valid && token.recaptcha_action_token.score < 0.5"
}
}
description = "Block low-score bot traffic to sensitive endpoints"
}В этой интеграции клиент отправляет поддерживаемый токен, а Cloud Armor применяет правило до бэкенда. Приложению всё равно нужны аутентификация, авторизация и защита от злоупотреблений.
Есть и собственно WAF-режим в Cloud Armor, называемый действиями управления ботами (redirect, googleRecaptcha), который выдаёт вызов reCAPTCHA прямо на периметре — полезно для сценария «подозрительный скачок трафика, бросаем вызов всем, пока не спадёт».
Только классификатор (без интеграции на стороне клиента)
Пограничные сервисы могут обнаруживать аномалии без виджета на каждой форме. Это отличается от проверки браузерного задания или оценки каждого запроса на бота.
Cloud Armor Adaptive Protection ориентирован на DDoS уровня 7 и предлагаемые правила защиты. Cloudflare Bot Management даёт сигналы ботов с помощью нескольких методов. Задачи пересекаются, но это не эквивалентные классификаторы каждого запроса.
Проверки усложняют работу и автоматизации, и обычным пользователям. Польза зависит от точки входа, атакующих, доступности и ложных срабатываний. Они не гарантируют, что запрос отправил человек.
- Применяйте меры к действиям с риском злоупотреблений: регистрации, входу, сбросу пароля.
- Проверяйте токены на сервере и явно обрабатывайте отсутствующие или ошибочные.
- Сочетайте проверки с лимитами и мониторингом.
- Защищайте административный доступ аутентификацией и авторизацией, при необходимости добавляя сетевые ограничения.
За периметром — сканирование зависимостей с OSV-Scanner
Сканирование зависимостей проверяет поставляемые библиотеки. Оно дополняет фильтрацию трафика, выявляя известные уязвимые версии для оценки и, при необходимости, обновления.
Почему OSV-Scanner
OSV-Scanner проверяет версии по базе OSV, объединяющей сведения разных экосистем, включая GitHub Security Advisories. Источники и охват пакетов у сканеров различаются.
Он читает распространённые lock-файлы (package-lock.json, yarn.lock, Pipfile.lock и т. д.), разрешает каждый пакет в его точно закреплённой версии и сообщает об уязвимостях с идентификаторами CVE, серьёзностью и (там, где OSV это знает) диапазоном версий, который их исправляет.
Запускаем сканирование и читаем вывод
OSV-Scanner можно установить через Go, Homebrew, готовым бинарником или — так же, как gitleaks выше — запустить через Docker. Для единообразия покажем Docker.
Я только что прогнал это по репозиторию, в котором живёт эта статья:
docker run --rm -v "$(pwd):/src" ghcr.io/google/osv-scanner:latest \
scan source --recursive /src--recursive проходит по каждому каталогу под /src и находит lock-файлы во вложенных проектах (блог, несколько демо, сервер, черновые эксперименты). Без него сканер осматривает только верхний уровень — нормально для однопроектного репозитория, но у большинства настоящих lock-файлы разбросаны по нескольким каталогам.
Сводка в конце прогона выглядит так:
Total 35 packages affected by 59 known vulnerabilities (2 Critical, 17 High, 37 Medium, 3 Low, 0 Unknown) from 2 ecosystems.
59 vulnerabilities can be fixed.В сохранённом сканировании есть следующие находки. Это снимок репозитория и базы на момент проверки, а не описание текущего checkout.
| OSV URL | CVSS | Экосистема | Пакет | Версия | Исправленная версия | Источник |
|---|---|---|---|---|---|---|
| GHSA-xq3m-2v4x-88gg | 9.4 | npm | protobufjs | 7.5.4 | 7.5.5 | blog/…/transformers-js-demo/package-lock.json |
| GHSA-p9ff-h696-f583 | 8.2 | npm | vite | 7.3.1 | 7.3.2 | blog/package-lock.json |
| PYSEC-2025-40 | 7.5 | PyPI | transformers | 4.48.3 | 4.49.0 | blog/…/from-scratch/requirements.txt |
| GHSA-r5fr-rjxr-66jc | 8.1 | npm | lodash | 4.17.23 | 4.18.0 | server/package-lock.json |
OSV URL— ведёт прямо к уведомлению на osv.dev (идентификатор CVE, вектор атаки, диапазон затронутых версий, ссылки).CVSS— оценка серьёзности по шкале 0–10. 9+ это Critical, 7–8.9 это High, 4–6.9 это Medium.Ecosystem— из какого реестра пакетов приходит зависимость. Одно и то же имя пакета может существовать в нескольких экосистемах и получать разные CVE.PackageиVersion— что сейчас закреплено в lock-файле.Fixed version— наименьшая версия, решающая проблему. Часто патч-обновление; иногда минорное или мажорное.Source— из какого lock-файла пришла находка. Критично для монорепозиториев: один и тот же пакет может быть закреплён в разных версиях в разных подпроектах, что мы ровно и видим дляvite,protobufjsиpicomatchв реальном сканировании.
Обратите внимание, что сводка разбивает счёт двумя способами: 59 известных уязвимостей всего и 59 уязвимостей можно исправить — то есть у каждой есть известная цель обновления. В этом сканировании они совпали, что бывает не всегда.
Исправленная версия — кандидат для обновления, а не доказательство совместимости или полноты исправления. Изучите описание, ветки выпусков и транзитивные зависимости, затем протестируйте изменение.
OSV-Scanner также работает с lockfile, SBOM и образами. Синтаксис установленной версии и требования доступа к контейнерам см. в документации.
Что делать с находками
Прогоните его один раз по легаси-приложению — и почти наверняка получите стену находок: легко десятки, иногда сотни. Для каждой находки что делать решают два вопроса: насколько это серьёзно и есть ли доступное исправление.
Приоритизируйте активно эксплуатируемые и доступные уязвимости с учётом серьёзности, достижимости кода, данных и мер защиты. Низкий балл не оправдывает игнорирование, а высокий сам по себе не задаёт одинаковый срок для всех систем.
Находки без подходящего исправления требуют мер снижения риска и дальнейшего наблюдения:
Если подходящего обновления нет, рассмотрите отключение функции, ограничение доступа или замену зависимости. Правило WAF может временно помочь лишь при атаке через проверяемый им трафик. Для исключения укажите ответственного и дату пересмотра: скрытие находки не устраняет уязвимость.
Альтернативы и когда выбирать какую
Другие варианты — Artifact Analysis для поддерживаемых образов, Trivy для разных артефактов и конфигураций и Dependabot для уведомлений и PR обновления. Включите нужные функции и проверьте охват экосистем; это не взаимозаменяемые проверки одинаковых данных.
Разумный минимальный набор: OSV-Scanner в CI (блокирует сборку на новых находках высокой серьёзности) + Dependabot (непрерывно открывает PR на обновление). Если вы уже поставляете контейнеры, либо добавьте Trivy для сканирования образов, либо — если пушите в облачный реестр — опирайтесь на встроенный сканер реестра (Artifact Analysis на GCP, ECR + Inspector на AWS), а не эксплуатируйте ещё один инструмент.
Сканеры — это не гигиена цепочки поставок
Поиск известных уязвимостей не обеспечивает полной защиты цепочки поставок. Некоторые базы содержат известные вредоносные пакеты, но не гарантируют выявление нового бэкдора, захваченного аккаунта сопровождающего или скрипта установки. Проверяйте изменения зависимостей и ограничивайте доступ при установке.
NPM Security Cheat Sheet от OWASP — полезный конкретный чек-лист для этого, если ваш стек Node.js: --ignore-scripts, npm audit, пакеты с областью имён, распознавание опечаток и прочее. Vulnerable Dependency Management Cheat Sheet от OWASP покрывает то же самое без привязки к языку. Оба хорошо сочетаются со слоем на основе сканеров выше: сканеры для известно плохого, гигиена для неизвестно плохого.
Что этот набор покрывает и куда можно пойти дальше
Если вы развернёте три основных слоя плюс сканирование зависимостей, что вы себе на самом деле купили?
Что могут уменьшить эти меры: утечки секретов, эксплуатацию известных зависимостей после исправления, запросы с распознаваемыми атаками и часть автоматизированных злоупотреблений. Охват зависит от настроек, ограничений обнаружения и реакции на находки.
Всё ещё упущено:
- Уязвимые шаблоны внутри вашего собственного кода. WAF блокирует полезные нагрузки во время запроса, но не помогает вам найти или исправить лежащий под ними уязвимый код. SAST и DAST ниже — то, с чего вы начнёте это делать.
- Изъяны логики приложения — всё, что требует знания того, что приложение должно делать. IDOR (запрос к
/orders/1234, когда пользователь должен видеть только1233), сломанная аутентификация или управление сессиями, отсутствующие или несогласованные проверки авторизации, ошибки бизнес-логики вроде пропуска шага оплаты повторной отправкой запроса оформления корзины. Ни одно из этого не выглядит злонамеренным для сканера. - Новые уязвимости нулевого дня в приложении или его зависимостях, до появления сигнатур.
- Упорные противники. Инсайдеры, злоупотребляющие законным доступом. Хорошо обеспеченные операторы ботов, которые платят сервисам решения CAPTCHA и меняют IP быстрее, чем вы успеваете их блокировать.
Что добавить следующим: SAST и DAST
Когда три основных слоя и сканирование зависимостей на месте, естественный следующий шаг — анализировать сам код, а не только трафик, текущий через него. Две дополняющие друг друга техники:
- SAST анализирует код без запуска. Semgrep и CodeQL находят поддерживаемые шаблоны уязвимостей, но требуют подходящих правил и проверки результатов.
- DAST исследует работающее приложение. ZAP предлагает пассивный анализ и активные тесты. Даже базовый скан обходит приложение и создаёт трафик; используйте разрешённую цель и учитывайте побочные эффекты маршрутов. Активные тесты проводите в изоляции с одноразовыми данными.
Используйте разрешённые аутентифицированные сеансы DAST для доступа к защищённым маршрутам. Сочетайте сканирование с проверкой авторизации и бизнес-логики, которую автоматизация может пропустить.