bandit: проверка кода Python на уязвимости и запуск в CI

0
9
Обложка статьи: bandit: проверка кода Python на уязвимости и запуск в CI

Зависимости проверяет pip-audit, а ошибки в собственном коде ищет статический анализатор. Для Python самый простой вариант — bandit: он разбирает код без запуска и отмечает опасные конструкции (внедрение в SQL и команды, небезопасная десериализация, слабые хэши, отключённая проверка сертификатов, зашитые пароли). Ниже порядок внедрения и результаты проверки на тестовом файле.

Статья опирается на логику направления «Управление уязвимостями» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Проверка выполнена в временном виртуальном окружении (bandit 1.9.4), после неё всё удалено.

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

Уровень Что это значит на практике
2. Гигиенический Ошибки безопасности в собственном коде ищутся автоматически при каждой сборке, находки получают владельцев и сроки

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

Тестовый файл с типовыми ошибками

Чтобы увидеть работу анализатора, используем небольшой файл src/app.py со специально допущенными ошибками (токен в нём вымышленный):

import hashlib
import pickle
import subprocess
import sqlite3
import yaml

API_TOKEN = "example-token-not-real"


def find_user(conn, name):
    return conn.execute("SELECT * FROM users WHERE name = '%s'" % name).fetchall()


def ping(host):
    return subprocess.run("ping -c 1 " + host, shell=True)


def load_config(text):
    return yaml.load(text)


def load_session(blob):
    return pickle.loads(blob)


def password_hash(p):
    return hashlib.md5(p.encode()).hexdigest()


def fetch(requests, url):
    return requests.get(url, verify=False)

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

python3 -m venv /tmp/bv
/tmp/bv/bin/pip install bandit
/tmp/bv/bin/bandit -r src -c bandit.yaml

Результат проверки

Всего 18 строк кода и 10 находок: 3 высокой, 4 средней и 3 низкой серьёзности.

Код Что нашёл Серьёзность Как исправить
B602 subprocess с shell=True и строкой от пользователя Высокая Передавать список аргументов без shell: ["ping", "-c", "1", host] и проверять host
B324 MD5 для паролей Высокая Для паролей: argon2, bcrypt или scrypt
B501 verify=False в запросе Высокая Убрать; при своём центре сертификации указать verify="/путь/ca.pem"
B608 SQL собирается подстановкой строки Средняя Параметризованный запрос: execute("... WHERE name = ?", (name,))
B506 yaml.load без безопасного режима Средняя yaml.safe_load
B301 pickle.loads для данных извне Средняя Использовать JSON или подписывать данные; не распаковывать чужой pickle
B113 Запрос без таймаута Средняя (уверенность низкая) Указывать timeout=
B105 Токен в коде Низкая Хранить в переменных окружения или хранилище секретов: секреты в git
B403, B404 Сам факт импорта pickle и subprocess Низкая Информационные, обычно шум

Обратите внимание: зашитый в код токен получил низкую серьёзность, поэтому порог «средняя и выше» его пропустит. Поиск секретов лучше поручить отдельному инструменту, а не рассчитывать на bandit.

Пороги и запуск в конвейере

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

exclude_dirs: [tests, .venv]
skips: []
#!/usr/bin/env bash
# Проверка кода Python на типовые ошибки безопасности. Падает на находках средней и высокой серьёзности.
set -u
TARGET="${1:-.}"
bandit -r "$TARGET" -c bandit.yaml --severity-level medium --confidence-level medium -f txt
rc=$?
case $rc in
  0) echo "bandit: находок средней и высокой серьёзности нет" ;;
  1) echo "bandit: есть находки, сборку нужно остановить" ;;
  *) echo "bandit: ошибка запуска (код $rc)" ;;
esac
exit $rc

Коды возврата: 0 — находок выше порога нет, 1 — есть находки, другие значения — ошибка запуска. В тесте скрипт вернул 1 и показал шесть находок (B113 с низкой уверенностью и низкие по серьёзности в этот список не попали), а на чистом файле вернул 0.

Подавление ложных срабатываний

Если срабатывание не применимо, добавьте в строку комментарий # nosec B501 с кодом теста. В проверке такая запись скрыла B501, а B113 в той же строке осталась, потому что подавляется только указанный код (в итоговой статистике это строка «skipped due to specifically being disabled»). Комментарий # nosec без кода скрывает всё в строке, так делать не стоит. Каждое подавление должно сопровождаться причиной и проверяться на ревью.

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

  1. Запустите на всём проекте без порога, сохраните отчёт как исходное состояние (-f json для обработки).
  2. Исправьте высокие находки в коде, доступном извне, затем средние.
  3. Включите порог в CI (средняя серьёзность и уверенность), чтобы новые ошибки не попадали в основную ветку.
  4. Оформите исключения: # nosec КОД с комментарием о причине.
  5. Исключите ненужные каталоги (тесты, виртуальное окружение) через exclude_dirs.
  6. Добавьте проверку зависимостей и секретов: pip-audit и поиск секретов.
  7. Пересматривайте результаты регулярно: сроки исправления берите из общего процесса.

Диагностика

Симптом Причина и что делать
expected '<document start>' ... Error parsing file Ключ -c ждёт файл в формате YAML. Файл .bandit в формате INI так не читается; пишите настройки в bandit.yaml
В отчёте сотни находок в чужом коде Проверяется виртуальное окружение внутри каталога. Укажите целевой каталог с кодом (src) и добавьте exclude_dirs
Много B101 (assert) Это тесты или чужой код. Исключите каталог тестов
Секрет в коде не найден на среднем пороге B105 имеет низкую серьёзность. Используйте отдельный сканер секретов
Находка осталась после # nosec Указан другой код теста или комментарий стоит не в той строке. Сверьте код в отчёте
Сборка падает на каждом коммите Порог слишком строгий для старого проекта. Начните с высоких находок, сохраните исходный отчёт и сравнивайте с ним

Ограничения

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

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

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

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

  1. Bandit запускается в конвейере при каждом изменении кода, порог описан в репозитории.
  2. Тестовая ошибка (например, shell=True со строкой от пользователя) останавливает сборку.
  3. Исключения оформлены комментарием с кодом и причиной, их список просматривается на ревью.
  4. Находки высокой серьёзности имеют владельца и срок исправления.

Источники

  • Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
  • Документация bandit: bandit.readthedocs.io; исходный код: github.com/PyCQA/bandit.
  • OWASP Top 10: A03 Injection, A08 Software and Data Integrity Failures: owasp.org. Международная практика, не российский норматив.

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