Я работал в компании, где один junior закоммитил SQL-инъекцию прямо в production. Утром пришёл alert, днём уже была инцидент-постмортем. Мог бы не быть, если бы у них работало нормальное SAST сканирование. Но давайте по порядку.
SAST (Static Application Security Testing) — это анализ кода без его выполнения. Инструмент смотрит на исходник, находит потенциальные уязвимости, и кричит об этом. Звучит просто, но в деталях куча нюансов.
Что вообще ловит SAST и почему это важно
SAST работает на уровне исходного кода. Инструмент парсит дерево синтаксиса, ищет опасные паттерны, отслеживает потоки данных. Это не блэк-бокс тестирование — это белый ящик, где видно всё.
Что он может найти:
Уязвимости ввода-вывода. SQL-инъекции, XSS, command injection — когда пользовательские данные напрямую попадают в опасные функции без валидации. По моему опыту, это топ-3 уязвимостей в production коде. Вот пример:
# Так не надо
user_id = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_id}"
db.execute(query)
# SAST сразу заметит, что user_id не экранирован
Хардкод секретов. API ключи, пароли, токены прямо в коде. Я видел, как разработчик добавил AWS ключ в переменную окружения, а потом закоммитил .env файл. SAST ловит такое с вероятностью 80-90%.
Небезопасное использование криптографии. Слабые алгоритмы хеширования, жёсткие IV для шифрования, использование MD5 для паролей — классика.
Race conditions и проблемы с конкурентностью. Сложнее ловить, но хороший SAST справляется.
Проблемы десериализации. Когда десериализуешь JSON без проверки типов, может быть беда.
Статистика: по данным OWASP, примерно 70% уязвимостей можно было бы найти статическим анализом ещё на этапе разработки. Но большинство компаний начинают проверку только перед релизом или, о ужас, после инцидента.
SAST vs DAST: в чём разница и почему оба нужны
Часто путают SAST и DAST. Дай я расскажу про каждый.
SAST — статический анализ. Смотрит на код, не запуская его. Быстро, но иногда много ложных срабатываний. Ловит проблемы рано, в IDE разработчика.
DAST — динамический анализ. Запускает приложение, отправляет в него фаззинг, ловит ошибки в runtime. Более реалистичен, но медленнее. И сложнее интегрировать в CI/CD.
По-хорошему, нужны оба. SAST — для быстрого feedback на этапе разработки. DAST — для глубокого тестирования перед production.
# Типичный pipeline с обоими проверками
stages:
- build
- sast
- test
- dast
- deploy
sast_scan:
stage: sast
script:
- semgrep --config=p/security-audit src/
artifacts:
reports:
sast: sast-report.json
dast_scan:
stage: dast
script:
- owasp-zap-scan http://localhost:8080
artifacts:
reports:
dast: dast-report.json
На одном проекте в Яндексе мы внедрили обе проверки. SAST ловил 90% проблем на этапе MR, DAST находил оставшиеся 10% и некоторые логические уязвимости, которые SAST не видит.
Инструменты SAST: что выбрать и как не облажаться
На рынке куча инструментов. Дам краткий обзор.
Semgrep — мой фаворит для стартапов и небольших команд. Open source, быстро, гибкие правила. Пишешь свои проверки на простом DSL. Бесплатный, и enterprise версия тоже разумная. Я использовал его на трёх проектах — нареканий не было.
# Установка и использование
pip install semgrep
semgrep --config=p/security-audit --json -o report.json src/
SonarQube — тяжёлая артиллерия. Хорош для больших компаний с сложной структурой кода. Красивый Dashboard, интеграция со всем. Но дорогой и требует сервера.
Checkmarx — коммерческий, мощный. Хорош если у вас серьёзные требования по безопасности (финтех, госконтракты). Дорого.
Bandit (для Python), ESLint с плагинами (для JS), FindBugs (для Java) — специализированные инструменты. Проще настроить под язык, но узкие.
Snyk — находит уязвимости в зависимостях. Это не совсем SAST в классическом смысле, но очень полезно.
Если честно? Большинство команд начинают с Semgrep. Это оптимальное соотношение мощности и простоты. Потом, когда растут, переходят на SonarQube или Checkmarx.
Типичные уязвимости, которые ловит SAST
Давай разберу конкретные примеры уязвимостей и как их находить.
SQL-инъекция
Классика жанра. Пользователь передаёт параметр, он попадает прямо в query.
# Уязвимо
username = request.form.get('username')
password = request.form.get('password')
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
result = db.execute(query)
# SAST видит, что username и password используются в f-string без экранирования
# Пользователь вводит: admin' --
# Запрос становится: SELECT * FROM users WHERE username='admin' --' AND password='...'
# Пароль не проверяется, вход как admin
Как это ловить:
# Правильно
query = "SELECT * FROM users WHERE username=? AND password=?"
result = db.execute(query, (username, password))
SAST ищет паттерны типа f"SELECT ... {variable}" или "... " + variable + " ..." и флагирует их.
XSS (Cross-Site Scripting)
Когда вывод пользовательского контента не экранирован.
// Уязвимо
const comment = req.query.comment;
res.send(`<div>${comment}</div>`);
// Пользователь отправляет: <img src=x onerror="alert('hacked')">
// В HTML попадает: <div><img src=x onerror="alert('hacked')"></div>
// Скрипт выполняется в браузере у каждого, кто откроет страницу
// Правильно
const comment = req.query.comment;
res.send(`<div>${escapeHtml(comment)}</div>`);
// Или в шаблонизаторе (вроде Handlebars, EJS)
// они экранируют по умолчанию: {{comment}}
SAST ищет innerHTML, eval, и прямые вставки в HTML без экранирования.
Хардкод секретов
# Уязвимо
AWS_KEY = "AKIAIOSFODNN7EXAMPLE"
DB_PASSWORD = "mypassword123"
API_TOKEN = "ghp_1234567890abcdefghijklmnopqrstuvwxyz"
SAST сканирует на regex паттерны AWS ключей, GitHub токенов, и другие известные форматы. Современные инструменты (вроде git-secrets, truffleHog) это ловят с точностью 95%.
Unsafe deserialization
# Уязвимо
import pickle
data = request.data
user = pickle.loads(data) # Если data не от нас, это RCE
# Правильно
import json
data = request.data
user = json.loads(data) # JSON безопаснее
Использование известных уязвимых библиотек
Flask 1.0.0 имеет CVE-2019-1010083
Django 2.0.0 имеет CVE-2019-6975
requests 2.20.0 имеет уязвимость в SSL verification
SAST проверяет версии зависимостей против базы CVE.
Как интегрировать SAST в pipeline
Самое важное — это не просто запустить сканер, а встроить его в workflow так, чтобы не было боли.
Вариант 1: Semgrep в GitLab CI
stages:
- scan
- test
semgrep_scan:
stage: scan
image: returntocorp/semgrep
script:
- semgrep --config=p/security-audit
--json
-o sast-report.json
--gitlab-json
-o gitlab-sast.json
src/
artifacts:
reports:
sast: gitlab-sast.json
expire_in: 30 days
allow_failure: false
Вариант 2: SonarQube в pipeline
sonarqube_scan:
stage: scan
image: sonarsource/sonar-scanner-cli:latest
script:
- sonar-scanner
-Dsonar.projectKey=my-app
-Dsonar.sources=src/
-Dsonar.host.url=$SONAR_HOST_URL
-Dsonar.login=$SONAR_TOKEN
Вариант 3: Snyk для зависимостей
npm install -g snyk
snyk auth $SNYK_TOKEN
snyk test --severity-threshold=high
Ключевые моменты при интеграции:
-
Не блокируй сразу. Первые запуски SAST будут много ложных срабатываний. Используй
allow_failure: trueпервые 2-3 недели, пока не отфильтруешь noise. -
Baseline. Сначала просканируй весь codebase, создай baseline. Потом проверяй только новые коммиты.
-
Настройка правил. Не все уязвимости одинаково важны. Высокий приоритет SQL-инъекциям, XSS, command injection. Low priority — стиль кода, логирование.
-
Feedback разработчикам. Самое важное — быстрый feedback. Если SAST запускается 10 минут, разработчик забудет, над чем работал. Оптимально — до 2 минут.
-
Игнорирование ложных срабатываний. Иногда нужно явно заигнорировать проверку в коде:
# nosemgrep: python.lang.security.audit.sql-injection
query = f"SELECT * FROM logs WHERE id = {user_id}"
# Это безопасно, потому что user_id валидируется выше
Реальная история: как SAST спас нас от инцидента
На одном проекте мы внедрили Semgrep в конце спринта. Первый же запуск нашёл 47 потенциальных проблем. Большинство — ложные срабатывания, но три оказались реальными уязвимостями.
Одна из них: junior разработчик писал логирование ошибок. Логировал весь объект request, включая headers. А в headers могли быть Authorization токены. Если бы это попало в production, логи содержали бы чувствительные данные.
SAST заметил паттерн "логирование полного request объекта" и заметил. Мы исправили за 5 минут.
Без SAST это бы заметили только через полгода, когда кто-то посмотрел бы логи и сказал "стоп, тут токены!".
Когда SAST неэффективен
Честно скажу, SAST — не панацея.
Логические уязвимости. SAST не поймёт, что в твоём коде есть race condition, которая позволяет скупить все товары по цене 1 копейка. Это нужно тестировать динамически.
Конфигурационные ошибки. Если ты настроил AWS S3 bucket доступным для всех через web UI, SAST это не увидит.
Уязвимости на уровне архитектуры. Если у тебя нет rate limiting, SAST не поможет.
Контекстные уязвимости. Иногда код выглядит уязвимым, но благодаря другому коду выше, это безопасно. SAST часто тут ошибается.
Поэтому SAST — это первая линия защиты. Потом идут code review человека, тестирование, DAST, и security аудит перед production.
Как выбрать инструмент для твоего проекта
Стартап, 5-10 разработчиков, Python/JS: Semgrep + git-secrets. Бесплатно, быстро, достаточно.
Средняя компания, 20+ разработчиков, несколько языков: SonarQube Community (бесплатно) или Semgrep Pro. Нужен более красивый dashboard и история.
Большая компания, высокие требования по безопасности: Checkmarx или Fortify. Дорого, но мощно.
Критичны зависимости: Snyk обязательно добавь на верх всего остального.
Если ты настраиваешь SAST с ну
