Пару лет назад на одном проекте мы словили утечку данных через SQL-инъекцию. Классика. Параметр из формы просто подставлялся в запрос без санитизации. Разработчик был опытный, просто устал и не заметил. Code review тоже прошло мимо — все смотрели на логику, а не на безопасность.
После этого мы внедрили автоматическую проверку исходного кода на уязвимости. Инцидентов больше не было. Расскажу, как выстроить этот процесс так, чтобы он работал, а не просто "галочка в отчёте".
Что вообще такое уязвимость в коде
Уязвимость — это место в коде, которое атакующий может использовать для нарушения работы системы или доступа к данным. Не все баги — уязвимости. Но любая уязвимость начинается с бага.
По моему опыту, 80% проблем с безопасностью — это одни и те же паттерны. SQL-инъекции, XSS, небезопасная десериализация, хардкоженные секреты. OWASP Top 10 меняется год от года, но суть остаётся той же: разработчики делают похожие ошибки.
Честно? Большинство команд не думают о безопасности до первого инцидента. Или до аудита, который заказчик требует перед запуском. Это дорого и больно. Лучше ловить проблемы раньше.
SAST vs DAST: статический и динамический анализ
Есть два основных подхода к анализу исходного кода на уязвимость.
SAST (Static Application Security Testing) — анализ кода без его запуска. Сканер смотрит на исходники, ищет паттерны уязвимостей. Работает на этапе написания кода, до компиляции и деплоя.
DAST (Dynamic Application Security Testing) — анализ работающего приложения. Сканер "атакует" запущенный сервис, ищет уязвимости в рантайме.
Какой лучше? Оба нужны. SAST ловит проблемы рано, но даёт много false positives. DAST видит реальную поверхность атаки, но требует запущенного окружения и находит проблемы поздно.
По-хорошему, SAST должен работать в CI/CD на каждый MR. DAST — на staging перед релизом или по расписанию.
Типичные уязвимости и как их искать
Давайте по конкретным примерам. Вот что чаще всего находится при проверке исходного кода на уязвимости.
SQL-инъекции
Классика, которая не умирает.
# Плохо — конкатенация строк
query = f"SELECT * FROM users WHERE id = {user_id}"
# Плохо — форматирование строки
query = "SELECT * FROM users WHERE id = %s" % user_id
# Хорошо — параметризованный запрос
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
SAST-сканеры отлично ловят этот паттерн. Они смотрят, как формируется запрос и есть ли санитизация входных данных.
XSS (Cross-Site Scripting)
// Плохо — прямая вставка user input
element.innerHTML = userInput;
// Хорошо — textContent не интерпретирует HTML
element.textContent = userInput;
// Или санитизация
element.innerHTML = DOMPurify.sanitize(userInput);
Хардкоженные секреты
Это моя "любимая". Когда-то нашёл AWS credentials в публичном репозитории. Команда даже не знала, что они туда попали.
# Так делать нельзя
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "super_secret_pass"
# Так можно — переменные окружения
import os
API_KEY = os.environ.get("API_KEY")
DB_PASSWORD = os.environ.get("DB_PASSWORD")
Сканеры ищут паттерны ключей, токенов, паролей. Регулярки не идеальны, но 90% случаев ловят.
Небезопасная десериализация
# Опасно — pickle может выполнить произвольный код
import pickle
data = pickle.loads(user_input)
# Безопаснее — JSON для простых данных
import json
data = json.loads(user_input)
Инструменты для анализа исходного кода на уязвимость
Инструментов много. Разберём основные категории.
Open-source SAST:
- Semgrep — гибкий, быстрые правила, поддерживает много языков
- Bandit — для Python, прост в настройке
- ESLint security plugins — для JavaScript/TypeScript
- SpotBugs с FindSecBugs — для Java
- Gosec — для Go
Коммерческие SAST:
- SonarQube — классика, много плагинов
- Checkmarx — enterprise-уровень
- Snyk — фокус на зависимости и код
DAST:
- OWASP ZAP — open-source, мощный
- Burp Suite — стандарт индустрии для пентестов
На одном проекте мы начали с Bandit для Python-бэкенда. Просто добавили в pre-commit хук. Разработчики сразу начали видеть проблемы до пуша. Потом подключили Semgrep — он нашёл ещё с десяток мест, которые Bandit пропустил.
Как внедрить проверку в CI/CD
Вот практическая конфигурация для GitLab CI:
stages:
- test
- security
semgrep:
stage: security
image: returntocorp/semgrep
script:
- semgrep ci --config=auto
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
bandit:
stage: security
image: python:3.11
script:
- pip install bandit
- bandit -r src/ -f json -o bandit-report.json
artifacts:
reports:
sast: bandit-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Ключевой момент — запуск на MR, а не только на main. Разработчик должен видеть проблемы до мержа, а не после.
Но есть нюанс. Если pipeline красный на каждый MR из-за false positives — разработчики начнут игнорировать предупреждения. Или отключат проверки. Видел такое не раз.
Поэтому настройка правил критична. Начните с high-severity, добавляйте medium постепенно. Исключайте ложные срабатывания.
# semgrep.yaml — пример исключения
rules:
- id: avoid-dangerous-functions
patterns:
- pattern: eval(...)
message: "Avoid eval()"
severity: WARNING
paths:
exclude:
- tests/
- migrations/
Что делать с результатами анализа
Сканер нашёл 100 потенциальных уязвимостей. Что дальше?
Первое — отфильтровать false positives. Да, это работа. Но она необходима.
Второе — приоритизировать. Не все уязвимости одинаково опасны. SQL-инъекция в публичном API — критично. Hardcoded secret в тестовом файле — менее срочно, но тоже надо чинить.
Третье — чинить. Звучит банально, но команды часто закрывают глаза на отчёты. "Потом разберёмся". Потом — это когда уже взломали.
На практике хорошо работает интеграция сканера прямо в code review. Когда бот оставляет комментарий к MR с конкретной проблемой — это сложно проигнорировать.
Автоматический code review с анализом безопасности
Вот тут мы подходим к тому, что делает Distiq. Это AI-бот, который интегрируется в GitLab, GitHub и GitVerse, анализирует MR и оставляет инлайн-комментарии. В том числе — по безопасности.
В отличие от классических SAST-сканеров, AI понимает контекст. Он не просто ищет паттерны — он анализирует логику кода. Может найти уязвимость, которую статический анализатор пропустит.
Мы на последнем проекте подключили Distiq параллельно с Semgrep. Distiq нашёл проблему с правами доступа, которую Semgrep не увидел — потому что это не паттерн, а логическая ошибка в бизнес-коде.
Подключается за пару минут: добавляешь webhook, указываешь репозитории. Всё, бот начинает смотреть каждый MR. Серверы в России, данные не уходят за рубеж — для некоторых проектов это критично.
Резюме
Анализ исходного кода на уязвимость — не разовая акция, а процесс. SAST в CI/CD на каждый MR. DAST на staging. Обучение разработчиков. Итеративная настройка правил.
Начать можно с малого: один open-source сканер в pipeline, обработка только high-severity. Главное — начать. Безопасность кода не появляется сама по себе.
