Security6 мин чтения2026-03-06

SAST для Python: как ловить уязвимости до production

Статический анализ безопасности кода (Static Application Security Testing, SAST) — это не просто очередная галочка в чек-листе DevOps. Это реально спасает жопу,

Статический анализ безопасности кода (Static Application Security Testing, SAST) — это не просто очередная галочка в чек-листе DevOps. Это реально спасает жопу, когда в production уходит код с SQL-injection или десериализацией враждебного объекта.

Я не один раз видел, как команда обнаруживала критическую уязвимость только после того, как её уже эксплуатировали. Помню один проект в стартапе — писали на Python, пользовались pickle для кеша без валидации. Угадайте, что произошло на боевом сервере? Поэтому когда я слышу "сначала напишем, потом проверим", я уже знаю, чем это закончится.

Давайте разберёмся, как SAST работает для Python, какие инструменты реально помогают, и как встроить проверки в CI/CD без бюрократии.

Что вообще такое SAST и почему Python нужен особый подход

SAST анализирует исходный код без его запуска, ищет паттерны, которые пахнут уязвимостью. Для Python это сложнее, чем для Java или Go, потому что:

Язык динамический. Тип переменной может измениться в runtime. Статический анализатор не всегда понимает, что вы туда подадите.

Много магии через рефлексию и eval-подобные конструкции. exec(), eval(), __import__() — всё это может быть законным, но может быть и дырой.

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

Но это не значит, что нужно сдаваться. Просто нужны правильные инструменты и понимание того, что ловить.

Основные типы уязвимостей, которые SAST ловит в Python

Давайте на конкретных примерах. Вот самые опасные паттерны:

SQL-injection

# Плохо — классика
user_id = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_id}"
db.execute(query)

SAST инструменты сразу видят f-string с переменной в SQL-запросе. Красный флаг.

# Хорошо
user_id = request.args.get('id')
query = "SELECT * FROM users WHERE id = ?"
db.execute(query, (user_id,))

Десериализация ненадежных данных

# Опасно
import pickle
data = request.get_data()
obj = pickle.loads(data)  # Враг может подсунуть вредоносный объект

Pickle может выполнить произвольный код при десериализации. SAST кричит. Используйте JSON или другие безопасные форматы.

# Лучше
import json
data = request.get_data()
obj = json.loads(data)

Использование eval и exec

# Убийственно плохо
user_input = request.args.get('expression')
result = eval(user_input)

Это вообще не нужно обсуждать. SAST будет ругаться на всё, что пахнет eval с внешними данными.

Жёсткие пароли и секреты в коде

# Попадётся в git
API_KEY = "sk-12345abcde"
DATABASE_PASSWORD = "admin123"

SAST инструменты ищут паттерны типа password =, api_key =, secret =. Если они содержат хардкодированные значения — флаг.

Использование небезопасных хешей

# Плохо для паролей
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()

MD5 и SHA1 для паролей — это преступление. Нужны bcrypt, argon2, scrypt.

# Правильно
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

Команды через shell без санитизации

# Опасно
import os
filename = request.args.get('file')
os.system(f"cat {filename}")  # Command injection

Враг просто передаст filename = "; rm -rf /" и ваша система пострадает.

# Безопасно
import subprocess
filename = request.args.get('file')
result = subprocess.run(['cat', filename], capture_output=True)

Инструменты SAST для Python: что реально работает

Bandit — король для Python-проектов

Это де-факто стандарт в Python-комьюнити. Специализируется именно на security-проблемах.

pip install bandit
bandit -r ./app

Выдаёт примерно такое:

>> Issue: [B602:shell_injection] Possible shell injection via Popen with shell=True
   Severity: HIGH   Confidence: MEDIUM
   Location: ./app/utils.py:12

Bandit знает про все уязвимости, что я выше перечислил. Плюс про рекурсивные вызовы функций с опасными параметрами.

Pylint с плагинами

Pylint — это больше про качество кода, но с плагинами вроде pylint-django может ловить и security-проблемы.

pip install pylint
pylint ./app

Но честно? Для security Bandit лучше. Pylint более универсальный.

Semgrep — мощный зверь для pattern-matching

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

pip install semgrep
semgrep --config=p/security-audit ./app

Это уже посерьёзнее, но и медленнее. Bandit быстрее работает.

Snyk — облачный SaaS с интеграцией в CI/CD

Хороший облачный сервис, но данные уходят в облако. Для российских компаний это может быть проблемой.

Pylint + Flake8 + mypy комбо

Если у вас нет выделенного security-инструмента, можно комбинировать:

Встраиваем SAST в CI/CD pipeline

Вот реальный пример для GitHub Actions:

name: Security Check
on: [push, pull_request]

jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      
      - name: Install dependencies
        run: |
          pip install bandit semgrep
      
      - name: Run Bandit
        run: bandit -r ./app -f json -o bandit-report.json
        continue-on-error: true
      
      - name: Run Semgrep
        run: semgrep --config=p/security-audit ./app
        continue-on-error: true
      
      - name: Upload reports
        uses: actions/upload-artifact@v3
        with:
          name: security-reports
          path: bandit-report.json

Для GitLab CI:

stages:
  - security

sast:
  stage: security
  image: python:3.10
  script:
    - pip install bandit
    - bandit -r ./app -f json -o bandit-report.json
  artifacts:
    reports:
      sast: bandit-report.json
    paths:
      - bandit-report.json
  allow_failure: true

Ключевой момент: allow_failure: true на начальном этапе. Иначе все существующие проблемы будут блокировать пайплайн. Лучше внедрять постепенно.

Настройка Bandit под реальный проект

Вот конфиг .bandit для игнорирования ложных срабатываний:

# .bandit
exclude_dirs:
  - '/tests/'
  - '/venv/'
  - '/.venv/'

tests:
  - B201  # flask_debug_true
  - B301  # pickle
  - B302  # marshal
  - B303  # md5
  - B304  # des
  - B305  # cipher
  - B306  # mktemp_q
  - B307  # eval
  - B308  # mark_safe
  - B309  # httpsconnection
  - B310  # url_open
  - B311  # random
  - B312  # telnetlib
  - B313  # xml_bad_etree
  - B314  # xml_bad_expat
  - B315  # xml_bad_sax
  - B316  # xml_bad_pulldom
  - B317  # xml_bad_etree
  - B318  # xml_bad_etree
  - B319  # xml_bad_etree
  - B320  # xml_bad_etree
  - B321  # ftplib
  - B322  # unverified_context
  - B323  # unverified_context
  - B324  # hashlib
  - B325  # tempnam

skips:
  - B101  # assert_used (много ложных в тестах)

Запуск с этим конфигом:

bandit -r ./app --ini .bandit

SAST vs DAST: когда нужно что

SAST анализирует код в статике — быстро, но может быть много ложных срабатываний.

DAST (Dynamic Application Security Testing) запускает приложение и пытается его взломать — медленнее, но реальнее.

Правильный подход: оба. SAST в каждый коммит, DAST перед production.

Для DAST в Python можно использовать:

Типичные ошибки при внедрении SAST

Включили всё сразу и заблокировали все MR. Результат: команда отключает проверки и ненавидит security.

Правильно: начните с высокого severity, потом добавляйте medium и low.

Игнорируют все срабатывания подряд. Это привыкание к noise.

Правильно: разберитесь с каждым срабатыванием, закройте issue или задокументируйте, почему это ложное срабатывание.

Не обновляют правила. Новые уязвимости появляются постоянно.

Правильно: обновляйте инструменты раз в месяц минимум.

Как Distiq помогает с SAST

Если вы уже используете Bandit или другие инструменты, но хотите, чтобы разработчики сразу видели проблемы в PR — это медленно и неудобно. Distiq автоматически анализирует каждый merge request и оставляет инлайн-комментарии с замечаниями. Не нужно ждать завершения пайплайна и копаться в артефактах. Бот сразу укажет на уязвимость прямо в коде — на строчке, где она есть.

Это работает для Python, JavaScript, Java и других языков. Интегрируется за две минуты с GitLab, GitHub или GitVerse. Серверы в России, данные не уходят за рубеж — что важно для compliance.

Берите и внедряйте SAST правильно.

Попробуйте Distiq для автоматического code review

AI-бот анализирует каждый MR/PR и оставляет комментарии с замечаниями. Интеграция за 2 минуты.

Попробовать бесплатно

Похожие статьи