На одном проекте мы нашли SQL-инъекцию через год после того, как она попала в продакшн. Классика — никто не делал code review с точки зрения безопасности, все смотрели на архитектуру и нейминг переменных. Уязвимость жила в малоиспользуемом фильтре отчётов. Когда узнали — было неловко.
После этого я начал разбираться с SAST. Оказалось, что большинство команд относятся к этому как к «ещё одной проверке в пайплайне», которая иногда кричит ложными срабатываниями. Но если разобраться, SonarQube SAST — это не про галочку в чек-листе, а про системный подход к безопасности.
Что такое SAST и чем отличается от DAST
SAST — Static Application Security Testing. Анализируем исходный код, не запуская его. Сканируем исходники, находим паттерны, которые могут привести к уязвимостям.
DAST — Dynamic Application Security Testing. Работаем с работающим приложением. Отправляем запросы, смотрим на ответы, пытаемся сломать.
Разница существенная. SAST находит проблемы на этапе написания кода — до коммита, до деплоя, до того как кто-то начнёт эксплуатировать. DAST ловит проблемы в рантайме, когда приложение уже где-то крутится.
SonarQube делает именно SAST. Парсит код, строит абстрактное синтаксическое дерево, ищет паттерны уязвимостей. Если коротко — знает, как выглядит плохой код, и сообщает вам об этом.
Как SonarQube SAST работает под капотом
SonarQube не просто ищет регулярками. Это было бы слишком просто и давало бы кучу ложных срабатываний.
Сначала парсер строит AST — абстрактное синтаксическое дерево. Потом анализатор проходит по дереву и отслеживает поток данных. Отслеживает, откуда приходят данные, куда уходят, не попадают ли они в опасные функции без санитизации.
Пример. Вот код с SQL-инъекцией:
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return db.execute(query)
SonarQube видит: переменная user_id приходит извне, попадает в строку запроса без обработки, эта строка уходит в db.execute. Трассировка показывает путь от источника до приёмника. Это и есть уязвимость.
Правильный вариант:
def get_user(user_id):
query = "SELECT * FROM users WHERE id = ?"
return db.execute(query, (user_id,))
Здесь параметризованный запрос. Данные и код разделены. SonarQube это понимает и не ругается.
Типичные уязвимости, которые ловит SonarQube
SQL-инъекции — самая очевидная категория. Но есть и другие.
XSS — Cross-Site Scripting. Вставляем пользовательский ввод в HTML без экранирования:
// Плохо
element.innerHTML = userInput;
// Хорошо
element.textContent = userInput;
Path traversal. Когда пользователь может указать путь к файлу:
# Плохо
filename = request.args.get('file')
with open(f"/var/data/{filename}") as f:
return f.read()
# Хорошо — валидация
import os
filename = request.args.get('file')
safe_path = os.path.join("/var/data", os.path.basename(filename))
Hardcoded credentials. Пароли и ключи прямо в коде:
# SonarQube это найдёт
DATABASE_PASSWORD = "super_secret_123"
API_KEY = "sk-live-abc123def456"
Использование небезопасных функций. В Python это eval(), exec(), pickle.loads() с непроверенными данными. В Java — десериализация непроверенных объектов.
Weak cryptography. MD5 для паролей, слабые ключи, неправильные режимы шифрования:
# Плохо
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()
# Хорошо
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
Настраиваем SonarQube для реального проекта
Честно? Из коробки SonarQube работает, но его нужно адаптировать под ваш стек.
Сначала определите quality profile. Для Java, Python, JavaScript есть готовые профили с правилами безопасности. Можно включить все — получите максимум покрытия, но и ложных срабатываний будет больше.
Я обычно делаю так: включаю все правила с severity Critical и Blocker. Правила с Major разбираю вручную и выключаю те, которые не подходят под архитектуру проекта.
Конфигурация через sonar-project.properties:
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=tests
sonar.python.version=3.9
sonar.exclusions=**/migrations/**,**/tests/**
sonar.issue.ignore.multicriteria=e1
sonar.issue.ignore.multicriteria.e1.ruleKey=python:S100
sonar.issue.ignore.multicriteria.e1.resourceKey=**/models.py
Здесь мы исключаем миграции и тесты из анализа и игнорируем правило нейминга в моделях. Потому что иногда оно не имеет значения.
Интеграция в CI/CD pipeline
SonarQube без автоматизации — бесполезен. Точнее, полезен, но ровно до тех пор, пока кто-нибудь не забудет запустить сканирование перед релизом.
В GitLab CI это выглядит так:
stages:
- test
- security
sonarqube-check:
stage: security
image: sonarsource/sonar-scanner-cli
script:
- sonar-scanner
-Dsonar.projectKey=${CI_PROJECT_NAME}
-Dsonar.sources=src
-Dsonar.host.url=${SONAR_HOST}
-Dsonar.login=${SONAR_TOKEN}
only:
- merge_requests
- main
В GitHub Actions похоже:
name: SonarQube Scan
on:
push:
branches: [main]
pull_request:
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST }}
Ключевой момент — fetch-depth: 0. SonarQube нужна история коммитов для корректного анализа новых изменений.
Quality Gates: блокируем или предупреждаем
Quality Gate — это набор условий, при которых сканирование считается пройденным или проваленным. Можно настроить жёстко: есть Critical уязвимость — билд падает.
Пример настроек Quality Gate:
- Новые Critical/Blocker issues: > 0 — провал
- Coverage on new code: < 80% — провал
- Duplicated lines on new code: > 3% — провал
Я рекомендую начинать мягко. Настроить уведомления, но не блокировать билды. Через месяц, когда команда привыкнет и разберёт накопившиеся проблемы — включать блокировку.
Иначе будет так: разработчики начнут ненавидеть SonarQube, найдут способы обойти проверки, и весь смысл потеряется.
Ложные срабатывания и как с ними жить
По моему опыту, где-то 15-20% найденных уязвимостей — ложные срабатывания. Код безопасен, но анализатор не понимает контекст.
Пример. SonarQube ругается на SQL-инъекцию:
def get_user(table_name):
# SonarQube видит переменную в SQL и кричит
query = f"SELECT * FROM {table_name} WHERE id = ?"
return db.execute(query, (user_id,))
Но если table_name приходит из конфига, а не от пользователя — это безопасно. Нужно объяснить SonarQube, что переменная доверенная:
ALLOWED_TABLES = ['users', 'orders', 'products']
def get_user(table_name):
if table_name not in ALLOWED_TABLES:
raise ValueError("Invalid table")
query = f"SELECT * FROM {table_name} WHERE id = ?"
return db.execute(query, (user_id,))
Теперь есть валидация. SonarQube это понимает и перестаёт ругаться.
Если всё равно ложное срабатывание — можно пометить как «Won't Fix» прямо в интерфейсе. Главное — не массово закрывать, не разбираясь. Иначе пропустите реальную уязвимость.
Ограничения SonarQube SAST
SonarQube не понимает бизнес-логику. Он найдёт SQL-инъекцию, но не поймёт, что пользователь может получить доступ к чужим данным через ID в URL.
SAST не видит уязвимости в сторонних библиотеках. Для этого нужен SCA — Software Composition Analysis. SonarQube умеет, но это отдельная история.
Сложные цепочки данных анализируются не всегда корректно. Если данные проходят через 5 функций и 3 файла — SonarQube может потерять след.
Ну и главное — SAST не заменяет DAST и пентесты. Он дополняет их. Защита в глубину, многослойный подход, все дела.
Альтернативы и когда они нужны
SonarQube — не единственный игрок. Есть Semgrep, CodeQL, Checkmarx, Snyk Code.
Semgrep — легковесный, быстро настраивается, хорош для кастомных правил. CodeQL — мощный, но сложный, от GitHub. Snyk Code — облачный, с хорошей интеграцией в IDE.
Выбор зависит от команды и процессов. SonarQube выигрывает у других, когда нужна самостоятельная установка, гибкая настройка правил и интеграция с существующей инфраструктурой.
Для российских компаний сейчас ещё и важен фактор локализации. SonarQube можно развернуть на своих серверах, данные не уходят за рубеж.
Если хотите пойти дальше автоматизации безопасности — посмотрите Distiq. Это AI-бот для code review, который встраивается в GitLab, GitHub и GitVerse. Он анализирует каждый MR, находит баги, уязвимости и проблемы с производительностью, оставляет инлайн-комментарии. Работает как второй глазами на ревью — только быстрее и не устаёт. Я бы использовал его вместе с SonarQube: статический анализ ловит паттерны, AI находит логические проблемы.
