pip-audit и хэши зависимостей: безопасность установки пакетов Python

0
11
Обложка статьи: pip-audit и хэши зависимостей: безопасность установки пакетов Python

Большинство приложений на Python состоит из чужого кода: десятки зависимостей, у каждой свои зависимости. Уязвимости в них находят постоянно, а установка пакета по имени без проверки позволяет подменить его (опечатка в названии, взломанный репозиторий). Две меры закрывают основную часть риска: регулярная проверка списка зависимостей на известные уязвимости утилитой pip-audit и установка с проверкой хэшей. Ниже порядок, результаты реальной проверки и скрипт для конвейера.

Статья опирается на логику направления «Управление уязвимостями» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Проверка выполнена в временном виртуальном окружении на Debian 12 с pip-audit 2.10 и доступом к индексу пакетов и базе уязвимостей; результаты зависят от даты запуска, так как база обновляется. Проверка зависимостей в других языках и в конвейере: Dependency-Track и SonarQube в GitLab CI; общий процесс: управление уязвимостями.

Что должно получиться

Уровень Что это значит на практике
2. Гигиенический Есть процесс с приоритетами и сроками закрытия уязвимостей, включая зависимости приложений

Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия.

pip-audit: проверка на известные уязвимости

Утилита сверяет версии пакетов из вашего списка с базами уязвимостей (PyPI Advisory Database и OSV). Устанавливайте её в отдельное окружение, чтобы не смешивать с зависимостями проекта:

python3 -m venv /tmp/audit-venv
/tmp/audit-venv/bin/pip install pip-audit
/tmp/audit-venv/bin/pip-audit -r requirements.txt --no-deps --disable-pip

Ключ -r читает файл зависимостей; --no-deps --disable-pip проверяют только перечисленные точные версии без их установки и без подтягивания зависимостей зависимостей (для этого в файле должны быть закреплены все пакеты, включая транзитивные). Тестовый файл с устаревшими версиями:

requests==2.19.1
flask==0.12.2
jinja2==2.10

Результат проверки (сокращён):

Found 30 known vulnerabilities in 3 packages
Name     Version ID              Fix Versions
-------- ------- --------------- ------------
requests 2.19.1  PYSEC-2018-28   2.20.0
requests 2.19.1  PYSEC-2023-74   2.31.0
requests 2.19.1  PYSEC-2026-1873 2.32.0
...

Три старых пакета дали 30 записей об уязвимостях (часть записей повторяется: один и тот же дефект описан в нескольких базах). В столбце Fix Versions указаны версии, в которых проблема исправлена: это цель обновления. Для чистого списка (six==1.16.0) результат No known vulnerabilities found.

Форматы и коды возврата

Что нужно Как Результат проверки
Машиночитаемый результат -f json JSON со списком пакетов и уязвимостей: на тесте 3 пакета, 30 записей
Остановка конвейера при находках код возврата утилиты 1, если уязвимости найдены; 0, если нет
Исправление --fix (при установленном окружении) Обновляет пакеты до исправленных версий; не применяйте без проверки совместимости

Для конвейера используйте скрипт: он создаёт временное окружение и возвращает код 1 при находках.

#!/usr/bin/env bash
# Проверка зависимостей Python на известные уязвимости; код возврата 1, если найдены (для CI).
# Использование: ./ci-check.sh requirements.txt
set -euo pipefail
REQ=${1:?файл requirements.txt}
python3 -m venv /tmp/audit-venv
/tmp/audit-venv/bin/pip install -q pip-audit
/tmp/audit-venv/bin/pip-audit -r "$REQ" --no-deps --disable-pip

Проверка скрипта: на файле с устаревшими версиями он вывел Found 30 known vulnerabilities in 3 packages и вернул 1; на файле с six==1.16.0 вывел No known vulnerabilities found и вернул 0.

Закрепление версий и хэшей

Проверка уязвимостей — это защита от известных дефектов. От подмены пакета защищает установка с проверкой хэшей: pip сравнивает контрольную сумму скачанного файла с указанной в списке. Режим включается ключом --require-hashes и автоматически, если у хотя бы одного пакета в файле указан хэш. Получение хэша: pip hash файл.whl или утилита pip-compile --generate-hashes из набора pip-tools, которая сразу формирует закреплённый список со всеми транзитивными зависимостями.

six==1.16.0 --hash=sha256:8abb2f1d86890a2d...
Проверка установки с --require-hashes Результат
Верный хэш Пакет установлен (six 1.16.0)
Неверный хэш (все нули) Ошибка: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE ... someone may have tampered with them; строки Expected и Got показывают ожидаемый и реальный хэши
Пакет без хэша в файле Ошибка: Hashes are required in --require-hashes mode, but they are missing from some requirements; pip выводит хэши скачанных архивов, чтобы добавить их

Важное ограничение: хэш защищает от подмены уже зафиксированного файла, но не от того, что вы зафиксировали вредоносный пакет. Поэтому хэши добавляют после проверки источника и сверки версий, а обновления идут через проверку pip-audit и ревью изменений списка зависимостей.

Порядок внедрения

  1. Закрепите версии всех зависимостей, включая транзитивные (pip freeze или pip-compile), и храните файл в репозитории.
  2. Включите хэши для рабочих сборок: pip-compile --generate-hashes и установка pip install --require-hashes -r requirements.txt.
  3. Запускайте pip-audit в конвейере при каждой сборке и по расписанию (раз в сутки или неделю): новые уязвимости появляются без изменения вашего кода.
  4. Определите сроки реакции: критические уязвимости в зависимостях, доступных извне, — дни; остальные — по общему процессу: сроки закрытия.
  5. Оформляйте исключения: если уязвимость не применима в вашем использовании, зафиксируйте это записью с владельцем и сроком пересмотра (--ignore-vuln ИДЕНТИФИКАТОР с комментарием в репозитории).
  6. Следите за источниками пакетов: используйте только известный индекс (или собственное зеркало), проверяйте названия новых зависимостей на опечатки.
  7. Храните секреты вне кода: секреты в git.

Диагностика

Симптом Причина и что делать
Много находок в старом проекте Давно не обновлялись зависимости. Сгруппируйте по пакетам, обновляйте поэтапно, начиная с доступных извне и критичных
pip-audit предупреждает о --no-deps Список не полностью закреплён. Используйте pip-compile --generate-hashes
Ошибка сети при проверке Нет доступа к индексу пакетов или базе уязвимостей. Проверьте прокси и разрешения для сборочного сервера
После закрепления хэшей установка падает на другой платформе Хэши зависят от файла (колесо для платформы). Формируйте список для каждой целевой платформы или включайте хэши всех нужных файлов
Обновление пакета сломало приложение Нет тестов или несовместимые версии. Обновляйте по одному, запускайте тесты, держите возможность отката

Где это в модели зрелости

Материал относится к направлению «Управление уязвимостями» (материалы направления, карта зрелости). Уровень 2 требует процесса с приоритетами и сроками: проверка зависимостей приложений и защита установки от подмены закрывают отдельную группу рисков, которую сканеры инфраструктуры не видят.

Как проверить

  1. Все зависимости закреплены по версиям, список находится в репозитории и проверяется ревью.
  2. pip-audit запускается в конвейере и по расписанию, находки приводят к задачам со сроками, исключения оформлены.
  3. Рабочие сборки используют --require-hashes; тестовая подмена хэша приводит к ошибке сборки.
  4. Источники пакетов известны и контролируются, новые зависимости проходят проверку.

Источники

  • Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
  • Документация pip-audit: github.com/pypa/pip-audit; режим проверки хэшей в документации pip: pip.pypa.io.
  • OWASP Top 10: A06 Vulnerable and Outdated Components: owasp.org. Международная практика, не российский норматив.

Оставьте свой ответ