Каждый день выпускаются тысячи строк кода. Каждый день в этом коде остаются баги безопасности. Их не видно на первый взгляд — уязвимость может скрываться в одной строке, которую никто не заметил на ревью.
SAST анализ (Static Application Security Testing) — это способ найти эти проблемы ещё до того, как код попадёт в production. Не требует запуска приложения, не требует тестировщиков безопасности. Просто анализирует исходник и говорит: "Вот тут опасно".
Я не зря начал с этого. За 10 лет в разработке я видел, как компании теряют деньги, репутацию и данные клиентов из-за банальных ошибок, которые SAST нашёл бы за миллисекунду. Поговорим о том, как это работает и как внедрить это в свой pipeline.
Что такое SAST и почему это не маркетинг
SAST — это просто анализ исходного кода без его запуска. Инструмент смотрит на текст программы и ищет паттерны опасного кода. Если ты пишешь eval() с пользовательским вводом — SAST это видит. Если забыл экранировать строку в SQL запросе — видит. Если используешь криптографию неправильно — видит.
Звучит как волшебство? Нет. Это просто правила. Хорошие правила.
Отличие SAST от динамического анализа (DAST) важное. DAST запускает приложение, отправляет ему запросы и смотрит, что сломается. DAST ловит уязвимости в runtime. SAST ловит их в коде, прямо во время написания.
Оба нужны. Но SAST дешевле, быстрее и работает на ранних стадиях — когда ещё можно что-то переделать без боли.
Как SAST анализ находит уязвимости
Механизм простой, но эффективный. Инструмент строит граф потока данных (data flow graph). Смотрит, откуда приходят данные (источник), как они преобразуются (трансформации) и куда попадают (сток).
Если опасные данные попадают в опасное место — это уязвимость.
Например, вот код на Python:
@app.route('/user/<user_id>')
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
result = db.execute(query)
return result
SAST видит: user_id приходит из внешнего источника (параметр URL), попадает прямо в SQL запрос без санитизации. Это SQL injection. Инструмент пометит это как HIGH severity.
Вот как это выглядит в коде:
Источник: user_id (пользовательский ввод)
↓
Трансформация: конкатенация в строку (никакой)
↓
Сток: db.execute() (опасное место)
= УЯЗВИМОСТЬ
SAST инструменты работают на нескольких уровнях:
Лексический анализ — просто смотрит на слова и символы. Находит опасные функции: eval(), exec(), system(). Это самое простое.
Синтаксический анализ — понимает структуру кода. Видит, что это функция, это переменная, это условие. Может отследить присваивания.
Семантический анализ — самый мощный. Понимает значение кода. Знает, что urllib.parse.quote() экранирует строку, а f-string — нет. Может отследить данные через несколько функций.
Статический анализ кода SAST использует все три уровня вместе.
Типичные уязвимости, которые ловит SAST
В моей практике 80% уязвимостей в production приложениях — это одни и те же ошибки, повторяющиеся снова и снова. SAST их знает.
SQL injection
Классика жанра. Когда пользовательский ввод попадает в SQL запрос без параметризации.
# УЯЗВИМО
email = request.args.get('email')
users = db.execute(f"SELECT * FROM users WHERE email = '{email}'")
# БЕЗОПАСНО
email = request.args.get('email')
users = db.execute("SELECT * FROM users WHERE email = ?", [email])
Разница в одной строке. SAST это видит сразу.
Command injection
Когда пользовательский ввод попадает в системные команды.
# УЯЗВИМО
filename = request.files['file'].filename
os.system(f"convert {filename} output.png")
# БЕЗОПАСНО
filename = request.files['file'].filename
subprocess.run(['convert', filename, 'output.png'], check=True)
Первый вариант позволяет вбить команду в filename. Второй передаёт параметры отдельно — нет интерпретации.
XSS (Cross-Site Scripting)
Когда данные пользователя выводятся на страницу без экранирования.
# УЯЗВИМО (Jinja2)
@app.route('/profile/<username>')
def profile(username):
return render_template('profile.html', user=username)
# profile.html:
# <h1>Welcome, {{ user }}</h1> -- если user содержит <script>, он выполнится
# БЕЗОПАСНО
# <h1>Welcome, {{ user | escape }}</h1>
SAST знает про Jinja2, Django, Flask шаблонизаторы и видит, когда данные не экранированы.
Небезопасная десериализация
# УЯЗВИМО
import pickle
data = request.get_data()
obj = pickle.loads(data) # Может выполнить произвольный код
SAST видит pickle.loads() с пользовательским вводом и кричит.
Криптографические ошибки
# УЯЗВИМО
import hashlib
password_hash = hashlib.md5(password).hexdigest() # MD5 уже не используется
# БЕЗОПАСНО
from werkzeug.security import generate_password_hash
password_hash = generate_password_hash(password)
Статический анализ кода Python видит устаревшие функции шифрования.
SAST инструменты: что выбрать
На рынке есть куча инструментов. Коммерческих, open-source, облачных, локальных.
SonarQube — король индустрии. Поддерживает 30+ языков, интегрируется везде, красивый интерфейс. Дорого для стартапов, но стоит свои деньги для крупных компаний.
Semgrep — быстрый, модульный, хорошо интегрируется в CI/CD. Можно писать свои правила. По-хорошему, лучший выбор для разработчиков.
Checkmarx — очень мощный, но сложный. Для больших корпораций с бюджетом.
Bandit (для Python) — простой, бесплатный, встроится за 5 минут. Для Python проектов рекомендую именно его.
pip install bandit
bandit -r . -f json > report.json
ESLint с security плагинами (для JavaScript) — стандарт де-факто.
npm install --save-dev eslint eslint-plugin-security
Но вот что я заметил: инструменты находят уязвимости. Люди их игнорируют. Потому что либо не понимают серьезность, либо интегрировать сложно, либо false positives надоедают.
Как внедрить SAST анализ в pipeline
Тут главное — не переусложнить. Инструмент должен встать в процесс разработки так, чтобы не мешать, но чтобы видел каждый коммит.
Вариант 1: SAST в CI/CD pipeline
# .gitlab-ci.yml
stages:
- analyze
- build
- test
security_scan:
stage: analyze
image: python:3.11
script:
- pip install bandit
- bandit -r src/ -f json -o bandit-report.json
- bandit -r src/ -f txt
artifacts:
reports:
sast: bandit-report.json
allow_failure: false # Не даём мержить, если нашлись проблемы
Инструмент запустится на каждый коммит. Если нашёл HIGH severity — пайплайн упадёт. Никто не смержит без фикса.
Вариант 2: Pre-commit hook
Проверка на локальной машине разработчика, ещё до пуша.
# .git/hooks/pre-commit
#!/bin/bash
bandit -r src/ 2>&1
if [ $? -ne 0 ]; then
echo "Security issues found. Commit aborted."
exit 1
fi
Разработчик видит проблему сразу, ещё не запушил.
Вариант 3: Автоматические комментарии в MR/PR
Вот тут начинается магия. SAST инструмент не просто находит уязвимость, но и оставляет комментарий прямо в коде:
Line 42: SQL injection vulnerability detected
user_id comes from external source and is used in SQL query without parameterization.
Suggestion:
- use = db.execute("SELECT * FROM users WHERE id = ?", [user_id])
Это работает через webhook и интеграцию с GitLab/GitHub. Разработчик видит проблему точно там, где она есть.
Как снизить false positives
SAST инструменты иногда ошибаются. Видят уязвимость там, где её нет. Это фрустрирует.
Например, Bandit может заругать вас за использование assert в тестах:
assert user.is_admin == True # Bandit скажет: "use of assert"
Но это же тесты! Assert тут нужен.
Решение — настраивать правила под свой проект. Отключать false positives, добавлять свои правила.
# .bandit
[assert_used]
skips = */tests/*
[hardcoded_sql_string]
enabled = False # Если у вас все запросы параметризованы, можно отключить
Или через комментарии в коде:
os.system(f"echo {message}") # nosec — говорим инструменту: я знаю, что делаю
Но будьте осторожны с nosec. Это как @SuppressWarnings в Java — иногда люди им злоупотребляют, и потом находят реальные уязвимости.
Сочетание SAST с другими методами
SAST — это не панацея. Это первая линия защиты.
После SAST нужны:
Code review — человек видит логику, которую не видит инструмент. Может заметить бизнес-логику уязвимость.
DAST тесты — запускаем приложение, отправляем вредоносные запросы, смотрим, сломается ли. Ловит то, что SAST не видит.
Penetration testing — настоящие хакеры пытаются взломать ваше приложение. Находят сложные комбинированные уязвимости.
Dependency scanning — проверяет, что ваши зависимости не содержат известных уязвимостей. Часто встроено в SAST инструменты.
В идеальном мире вы используете всё вместе. На практике начните с SAST и code review. Это даст максимум защиты на минимум затрат.
Практический пример: внедрение за неделю
Вот как я делал это на одном проекте. Python приложение, 50 разработчиков, никакой безопасности в pipeline.
День 1: Установил Bandit локально, запустил на коде. Получил 200+ warning'ов. Большая часть — false positives.
День 2-3: Настроил правила, отключил шум. Осталось 30 реальных проблем.
День 4-5: Разработчики фиксили проблемы. Большинство — в 5 минут.
День 6: Интегрировал в GitLab CI. На каждый MR запускается Bandit.
День 7: Написал документацию, провел встречу с командой.
Результат: за месяц после внедрения нашли и зафиксили SQL injection в админ-панели, которая работала в production полгода. Никто не видел.
Distiq: SAST в вашем GitLab/GitHub
Если честно, я устал настраивать инструменты вручную. Разные языки, разные конфиги, разные форматы отчётов.
Вот почему я рекомендую Distiq. Это AI-бот для code review, который умеет в SAST анализ. Интегрируется за 2 минуты — просто добавляешь webhook в репозиторий. Поддерживает Python, JavaScript, TypeScript, Java, Go и другие языки.
На каждый MR/PR бот анализирует код и оставляет инлайн-комментарии с замечаниями. Не просто находит уязвимости, но и объясняет, почему это опасно, и как это исправить. Российский сервис, данные не уходят за рубеж.
Для большинства команд — это именно то, что нужно. Меньше конфигов, больше безопасности.
