Перейти к основному содержанию
Блог

Серверы

Уязвимости зависимостей: composer, npm и безопасность цепочки поставок

Даже если ваш код чист, устаревшая библиотека делает его уязвимым. composer audit, npm audit, lock-файлы, Dependabot, заброшенные пакеты и SBOM — практическое руководство.

· 10 мин. чтения

Фото: AgainErick, CC BY-SA 4.0 · Wikimedia Commons

В современном веб-приложении код, написанный вами, — небольшая часть всего кода. Проект на Laravel приносит в папку vendor сотни пакетов, а простой фронтенд на React в node_modules — ещё больше. Каждый из них написан другими людьми, и в каждом может найтись уязвимость.

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

В чём проблема

Есть два вида риска:

  1. Известные уязвимости. В библиотеке находят уязвимость, ей присваивают CVE и выпускают исправленную версию. Если вы остались на старой версии, уязвимость открыта, а её детали известны всем. В OWASP Top 10 (2021) это отдельная категория: A06 — Vulnerable and Outdated Components.
  2. Атаки на цепочку поставок (supply chain). Вредоносным оказывается сам пакет: его имя сделано похожим на популярный пакет (typosquatting), аккаунт автора захвачен и в новую версию добавлен вредоносный код, или скрипт установки выполняет код на сервере либо на компьютере разработчика.

Первый риск привычен, и управлять им просто. Второй встречается реже, но его сложнее обнаружить.

Код, который вы не писали, — тоже ваш код. Для экспертизы vendor и node_modules — часть приложения.

Почему это важно для экспертизы

Сканеры экспертизы определяют версии библиотек, используемых приложением, разными способами:

  • JavaScript-библиотеки во фронтенде (например, старые версии jQuery или Bootstrap) определяются по имени файла и комментариям внутри.
  • Открытые файлы composer.lock, package.json или package-lock.json дают все пакеты и их версии одним списком.
  • HTTP-заголовки и страницы ошибок могут раскрывать версию фреймворка.

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

Проверьте:

for f in composer.json composer.lock package.json package-lock.json yarn.lock .env; do
  printf '%s  ' "$f"; curl -s -o /dev/null -w '%{http_code}\n' "https://site.uz/$f"
done

Все ответы должны быть 404 или 403. Если в Laravel веб-корнем служит папка public/, эти файлы естественным образом остаются снаружи. Если корень настроен неправильно, они будут открыты.

Как проверить самостоятельно

PHP: composer audit

Начиная с Composer 2.4 есть встроенная команда аудита. Она сверяет пакеты из composer.lock с базой известных уязвимостей:

composer audit
composer audit --no-dev          # только production-пакеты
composer audit --format=json     # для CI

Список устаревших пакетов:

composer outdated --direct

--direct показывает только пакеты, которые вы сами указали в composer.json, — при их обновлении часто обновляются и вложенные зависимости.

JavaScript: npm audit

npm audit
npm audit --omit=dev             # только production-зависимости
npm audit --audit-level=high     # только high и critical
npm outdated

Чтобы узнать, откуда приходит уязвимый пакет:

npm ls <имя-пакета>

npm audit fix обновляет до совместимых версий. npm audit fix --force может поднять и мажорную версию, что способно сломать приложение, — используйте его только вместе с тестами.

Полезно также проверить подписи пакетов из реестра npm:

npm audit signatures

Универсально: OSV-Scanner

Для проектов, где смешано несколько экосистем, OSV-Scanner проверяет все lock-файлы сразу по открытой базе OSV:

osv-scanner scan source -r .

Flutter и Dart

Для мобильных приложений принцип тот же:

flutter pub outdated

Сторонние SDK в мобильном приложении (аналитика, реклама, платежи) тоже являются зависимостями и могут проверяться на экспертизе.

Lock-файлы: воспроизводимая установка

composer.lock и package-lock.json фиксируют, какие именно версии устанавливаются. Без них при каждой установке могут приехать другие версии — в том числе новые, ещё не проверенные и потенциально вредоносные.

Правила:

  • Lock-файлы коммитятся в репозиторий.
  • В production и CI устанавливайте из lock-файла, не обновляя его:
composer install --no-dev --prefer-dist --optimize-autoloader
npm ci --omit=dev

npm install может изменить lock-файл, а npm ci строго следует ему и выдаёт ошибку, если он не соответствует package.json.

  • Просматривайте изменения lock-файлов на code review. Сотни строк изменений в lock ради маленькой правки — повод задать вопрос.

Автоматизация: Dependabot и Renovate

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

Для Dependabot на GitHub — .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      minor-and-patch:
        update-types: ["minor", "patch"]

Dependabot каждую неделю открывает pull request'ы с обновлениями, а предупреждения безопасности приходят отдельно. Renovate — похожий инструмент, работает и на GitLab, и на других платформах и позволяет тонко настраивать группировку и правила автоматического слияния.

Главное — не копить PR. Заведите привычку раз в неделю просматривать и сливать их; если тесты зелёные, не откладывайте патч-обновления.

Добавьте проверку и в CI, чтобы код с уязвимым пакетом не попал в production:

# Шаги GitHub Actions
- run: composer install --no-interaction --prefer-dist
- run: composer audit --no-dev
- run: npm ci
- run: npm audit --omit=dev --audit-level=high

Заброшенные пакеты

Уязвимость не опубликована, но автор прекратил поддержку пакета — это тоже риск. Когда найдут следующую уязвимость, исправления не будет.

Composer предупреждает о заброшенных (abandoned) пакетах при install и update. В Composer 2.7 и новее аудит проверяет и это, а политику можно задать в composer.json:

{
  "config": {
    "audit": {
      "abandoned": "fail"
    }
  }
}

В npm устаревшие (deprecated) пакеты при установке выводятся как npm warn deprecated — не игнорируйте эти предупреждения. Для каждого заброшенного пакета найдите замену или удалите его.

Защита цепочки поставок

  • Проверяйте имя пакета. Перед добавлением нового пакета посмотрите его официальное имя, число загрузок, репозиторий и последнюю активность. Разница в одну букву — признак typosquatting.
  • Ограничьте скрипты установки. Пакеты npm могут выполнять скрипты вроде postinstall. Если в CI они не нужны, используйте npm ci --ignore-scripts (но учтите, что некоторые пакеты без скриптов не работают).
  • Меньше зависимостей — меньше риска. Не добавляйте пакет ради задачи в одну строку.
  • Добавьте SRI к скриптам, загружаемым с CDN. Тогда при подмене файла на CDN браузер его не выполнит:
<script src="https://cdn.example.com/lib.min.js"
        integrity="sha384-..."
        crossorigin="anonymous"></script>
  • Не собирайте проект на production-сервере. Собирайте фронтенд в CI и выкладывайте на сервер готовый артефакт — node_modules и инструменты сборки на сервере не нужны.

SBOM: знайте, из чего состоите

SBOM (Software Bill of Materials) — список всех компонентов приложения и их версий в стандартном формате (CycloneDX или SPDX). Когда публикуется новая уязвимость, при наличии SBOM вы за несколько секунд отвечаете на вопрос «есть ли у нас этот пакет и какой версии?».

# npm (npm 10+)
npm sbom --sbom-format cyclonedx > sbom-npm.json

# Composer (через плагин CycloneDX)
composer require --dev cyclonedx/cyclonedx-php-composer
composer CycloneDX:make-sbom --output-file=sbom-php.json

Храните SBOM вместе с каждым релизом. Это полезно и при экспертизе, и в работе с заказчиками: вы можете точно показать состав приложения.

Короткий чек-лист

  1. composer audit --no-dev и npm audit --omit=dev без серьёзных уязвимостей.
  2. composer.lock и package-lock.json в репозитории, в production используются composer install и npm ci.
  3. Lock-файлы, package.json, .env не открываются через браузер.
  4. Во фронтенде нет старых jQuery, Bootstrap и других библиотек.
  5. Подключён Dependabot или Renovate, PR регулярно просматриваются.
  6. CI останавливает сборку при уязвимой зависимости.
  7. Заброшенные и deprecated пакеты заменены.
  8. У скриптов с CDN есть SRI, на production-сервере ничего не собирается.
  9. Для каждого релиза создаётся SBOM.

Итог

Управление зависимостями — не разовая работа, а процесс. Один раз запустить composer audit и npm audit и закрыть найденное — хорошее начало для экспертизы. Но в долгосрочной перспективе lock-файлы, автоматические обновления и проверки в CI держат приложение в безопасном состоянии каждый день. О ключах и паролях, которые хранятся в коде, мы писали в статье управление секретами и ключами.

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