Если ты думаешь, что security это забота тестировщиков — забудь. Половина уязвимостей проскакивает мимо всех потому что их ищут в конце процесса разработки. А искать нужно сразу, во время написания кода.
SAST (Static Application Security Testing) — это анализ исходного кода без его запуска. Просто смотрим на код и говорим: "Тут SQL injection", "Вот XSS", "А здесь — неправильная валидация". Звучит просто? На деле это самый эффективный способ поймать баги безопасности на ранней стадии.
За 10 лет в разработке я видел, как один упущенный SQL injection стоил компании недели переделки и потери доверия пользователей. Так что это не просто best practice — это необходимость.
Что такое SAST и почему это работает
SAST анализирует код статически — без выполнения. Инструмент смотрит на исходники, строит граф потока данных (data flow graph) и проверяет: есть ли опасные комбинации?
Например:
# Плохо
user_id = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_id}"
db.execute(query)
SAST инструмент видит: пользовательский ввод request.args.get() напрямую попадает в SQL запрос. Красная лампочка.
# Хорошо
user_id = request.args.get('id')
query = "SELECT * FROM users WHERE id = ?"
db.execute(query, (user_id,))
Тут ввод идёт через параметризованный запрос. Безопасно.
Главное преимущество SAST — скорость. Проверяем код за секунды, ещё до merge request. Не нужно дождаться deployment и пентеста.
Минус один: SAST не видит логику, которая работает только в runtime. Если уязвимость проявляется только в определённых условиях при работающей системе — SAST может её пропустить. Для этого есть DAST (Dynamic Application Security Testing), но это отдельная история.
Типичные уязвимости, которые ловит SAST
На одном проекте мы внедрили SAST и за первую неделю нашли 47 потенциальных проблем. Две из них были критичные. Рассказываю, что чаще всего попадает:
SQL Injection
Классика жанра. Пользовательский ввод в SQL запрос без экранирования.
# Плохо
username = request.form['username']
password = request.form['password']
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
result = db.execute(query)
Юзер вводит ' OR '1'='1 и получает доступ без пароля.
SAST видит это сразу. Решение простое — параметризованные запросы:
# Хорошо
username = request.form['username']
password = request.form['password']
query = "SELECT * FROM users WHERE username=? AND password=?"
result = db.execute(query, (username, password))
Cross-Site Scripting (XSS)
Когда данные пользователя выводятся в HTML без экранирования.
// Плохо
const comment = req.query.text;
res.send(`<div>${comment}</div>`);
Юзер пишет: <script>alert('hacked')</script> и скрипт выполняется в браузере каждого, кто смотрит этот комментарий.
// Хорошо
const comment = req.query.text;
const escaped = escapeHtml(comment);
res.send(`<div>${escaped}</div>`);
Или просто используй шаблонизаторы, которые экранируют по умолчанию (например, Jinja в Flask).
Hardcoded Credentials
Пароли и токены прямо в коде. Встречается чаще, чем ты думаешь.
# Плохо
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "admin123"
SAST инструмент видит строки, похожие на credentials, и бьёт тревогу. Решение: используй переменные окружения или secrets manager.
# Хорошо
import os
API_KEY = os.getenv('API_KEY')
DB_PASSWORD = os.getenv('DB_PASSWORD')
Path Traversal
Когда пользователь может указать любой путь к файлу на сервере.
# Плохо
filename = request.args.get('file')
with open(f"/uploads/{filename}") as f:
return f.read()
Юзер пишет ../../etc/passwd и получает доступ к файлам сервера.
# Хорошо
import os
from pathlib import Path
filename = request.args.get('file')
base_dir = Path("/uploads/")
file_path = (base_dir / filename).resolve()
# Проверяем, что файл внутри /uploads/
if not str(file_path).startswith(str(base_dir)):
return "Access denied", 403
with open(file_path) as f:
return f.read()
Неправильная валидация входных данных
Когда ты проверяешь только на клиенте или проверка неполная.
// Плохо
if (email.includes('@')) {
// отправляем письмо
}
Это не валидация. Нужна по-настоящему:
# Хорошо
from email_validator import validate_email, EmailNotValidError
try:
valid = validate_email(email)
email = valid.email
except EmailNotValidError:
return "Invalid email", 400
SAST инструменты: что выбрать
На рынке куча решений. Вот что я знаю по опыту:
SonarQube — король enterprise. Поддерживает 27 языков, отличная интеграция с CI/CD, удобная веб-морда. Минус: дорого и сложно настраивать.
Semgrep — мне нравится больше. Пишешь правила на простом YAML, проверяет быстро. Open source, легко интегрируется в любой pipeline.
# Пример правила Semgrep против SQL injection
rules:
- id: sql-injection
pattern-sources:
- patterns:
- pattern: request.args.get(...)
- pattern: request.form[...]
pattern-sinks:
- patterns:
- pattern: $DB.execute(f"...")
message: "SQL injection vulnerability"
severity: ERROR
Snyk — хорош для зависимостей. Ловит уязвимости в npm пакетах, pip библиотеках и т.д. Плюс ещё и SAST делает.
OWASP Dependency-Check — бесплатный, простой, хорошо работает для поиска уязвимостей в зависимостях.
Pylint, ESLint, Checkstyle — встроенные инструменты для каждого языка. Не full-featured SAST, но для базовых проверок достаточно.
Честно? Для большинства проектов хватает Semgrep + встроенные линтеры. Дополни OWASP Dependency-Check и спи спокойно.
Как встроить SAST в pipeline
Теория — теория, но главное это action. Вот как это работает в реальности:
# .gitlab-ci.yml пример
stages:
- scan
- test
- build
sast:
stage: scan
image: returntocorp/semgrep
script:
- semgrep --config=p/owasp-top-ten --json -o report.json .
- semgrep --config=p/owasp-top-ten .
artifacts:
reports:
sast: report.json
allow_failure: false
Или через GitHub Actions:
# .github/workflows/sast.yml
name: SAST Scan
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: returntocorp/semgrep-action@v1
with:
config: p/owasp-top-ten
После этого каждый push автоматически проверяется. Если нашли проблему — блокируем merge request. Разработчик видит конкретные замечания прямо в коде.
Важный момент: не делай SAST слишком strict на старых проектах. Если включишь все правила, получишь 500 ошибок и никто не будет слушать. Начни с критичных (SQL injection, XSS, credentials), потом постепенно добавляй остальное.
SAST vs DAST vs Pentesting
Частый вопрос: чем это отличается?
SAST смотрит на исходный код. Быстро, дешево, ловит много проблем на ранней стадии. Но не видит runtime уязвимостей и логических ошибок.
DAST запускает приложение и тестирует его как "чёрный ящик". Медленнее, но видит то, что не видит SAST. Например, проблемы с аутентификацией, которые проявляются только при взаимодействии компонентов.
Pentesting — это ручная работа квалифицированного специалиста. Дорого, долго, но находит самые хитрые уязвимости.
Правильный подход: SAST в каждом push, DAST в staging перед release, pentesting перед выходом в production или если у тебя финтех/медтех.
Практические советы для внедрения
По опыту, вот что реально помогает:
Сначала настрой SAST для поиска критичных уязвимостей. Не пытайся поймать всё сразу — это убьёт мотивацию команды.
Интегрируй в merge request. Когда разработчик видит замечание прямо в коде, он сразу его исправляет. Если отправлять отчёты в отдельный чат — забудут.
Обучи команду. Поставь у себя в Slack бота, который будет объяснять, почему это опасно и как исправить. Хотя бы для top-10 OWASP.
Регулярно обновляй rules. Новые уязвимости появляются постоянно. Если ты не обновил Semgrep пол года — пропустишь половину.
Не игнорируй findings. Я видел проекты, где в .semgrep.yml заблокирована половина правил потому что "генерируют false positives". Это не решение. Правильно настрой правила или уменьши sensitivity.
Когда SAST недостаточно
Честно? SAST ловит 60-70% типичных уязвимостей. Но есть вещи, которые он не видит:
Логические ошибки. Например, если твой код правильный, но неправильно реализует требования безопасности — SAST не поможет.
Race conditions в многопоточном коде. SAST видит статику, не динамику.
Проблемы с конфигурацией. Если ты неправильно настроил права доступа в облаке — SAST не найдёт.
Уязвимости в зависимостях, которых нет в базе данных инструмента. Здесь помогает OWASP Dependency-Check и Snyk.
Для этого и нужна комбинация инструментов. SAST — первая линия защиты, но не последняя.
Вот, собственно, весь смысл. SAST приложений — это не волшебство, это просто инструмент, который автоматизирует то, что раньше проверяли вручную на code review. И работает отлично.
Если ты ещё не встроил SAST в свой pipeline — начни с Semgrep и GitHub Actions (или GitLab CI). За час настроишь, за день найдёшь первые проблемы.
Кстати, в Distiq мы тоже используем SAST правила в нашем AI code reviewer. Когда ты создаёшь pull request, бот не только ловит стилистические проблемы, но и сканирует на безопасность — SQL injection, XSS, hardcoded credentials, всё как надо. Экономит время на code review и ловит то, что легко упустить человеку.
