ADG Оставить заявку
Блог Информационная безопасность 5 мин чтения

Встроенный SAST в GitLab CI: Semgrep находит SQL-инъекции в легаси, о которых не подозревали

Включаем SAST в GitLab Ultimate 13.x прямо в merge request pipeline: встроенный статический анализ, severity threshold и реальные находки в легаси-коде без блокировки деплоя.

Контекст момента

GitLab Ultimate 13.x интегрирует SAST/DAST/Dependency Scanning прямо в merge request pipeline без дополнительной настройки

В GitLab Ultimate 13.x появилась возможность запускать SAST, DAST и Dependency Scanning прямо в рамках merge request pipeline - без отдельных CI-систем и дополнительной настройки. Мы включили это на одном из клиентских проектов, где до этого статический анализ кода существовал только в разговорах о том, что "надо бы".

Что именно включилось

В GitLab 13.x для языков с поддержкой встроенного анализатора достаточно добавить в .gitlab-ci.yml:

include:
  - template: Security/SAST.gitlab-ci.yml

Pipeline автоматически подбирает анализатор под язык проекта. Для Python - bandit, для PHP - phpcs-security-audit, для Java - spotbugs, и так далее. Результаты появляются прямо в интерфейсе merge request: вкладка «Security» с перечнем находок, severity, местом в коде и ссылкой на правило.

Каждый анализатор работает на основе паттернов AST, а не просто текстового поиска, что даёт меньше ложных срабатываний по сравнению с grep-подходами. Насколько меньше - зависит от кодовой базы, но на нашей выборке был виден разрыв.

Что нашли в легаси

Проект - PHP-приложение, писалось примерно с 2013 года, несколько рук сменилось. Никакого статического анализа никогда не было. Включили SAST - и в первом же прогоне получили несколько десятков находок.

Часть ожидаемая: var_dump в продакшн-коде, eval на входных данных без валидации, md5 для хешей паролей. На эти грехи мы и рассчитывали наткнуться.

Но были и находки, которые никто в команде не отмечал как проблему. Несколько мест с прямой конкатенацией переменной в SQL-запрос:

// Найдено SAST: SQL Injection (CWE-89)
$query = "SELECT * FROM users WHERE login = '" . $_GET['user'] . "'";
$result = mysqli_query($conn, $query);

Код работал. Никто не падал на него при code review, потому что вокруг было много похожего кода, и глаз замылился. SAST это не пропустил.

Всего по категории High было несколько подобных мест - не десятки, но достаточно, чтобы отнестись серьёзно. Это был не тестовый стенд, а работающий продукт. О том, что там такое есть, разработчики честно сказали: «да, наверное есть, но где - не знаем».

Проблема с порогами

Здесь начинается организационная часть, которая оказалась важнее технической.

Если включить SAST и поставить pipeline на блокировку при любой находке уровня Medium и выше - деплой останавливается сразу. В легаси-проекте с несколькими десятками находок это означает: либо команда неделю разбирает их все перед первым merge, либо SAST тихо отключают и больше к нему не возвращаются. Второе случается чаще.

Мы выбрали другой подход. В GitLab CI можно настроить SAST_EXCLUDED_PATHS и переменную fail_on_severity, которая управляет тем, при каком уровне pipeline падает:

variables:
  SAST_EXCLUDED_PATHS: "tests/, vendor/"
  # Блокируем только Critical, Warning и Info - не блокируют
  SAST_FAIL_ON_UNKNOWN_SEVERITY: "false"

Реального fail_on_severity как переменной в этой версии шаблона нет - пришлось дорабатывать через allow_failure:

semgrep-sast:
  allow_failure: true  # не блокируем pipeline
  artifacts:
    reports:
      sast: gl-sast-report.json

Результаты при этом всё равно попадают в MR и показываются в Security-вкладке. Разработчик видит находки, но merge не блокируется. Блокировать решили только Critical - это настраивается через политики в GitLab Ultimate.

Такой режим позволил не останавливать работу пока разбираются исторические долги, при этом новые Critical-находки сразу видны и должны быть закрыты до merge.

Как это встраивается в процесс

После первого прогона мы провели разбор с командой: прошлись по находкам, отсортировали по severity, выделили реальные проблемы от информационного шума. High и Critical оформили как задачи в трекере с указанием конкретного файла и строки - GitLab это удобно позволяет прямо из интерфейса.

Находки уровня Info (например, var_dump в не-продакшн ветке) - добавили в исключения через конфигурацию.

Через две недели разработчики сами стали смотреть на Security-вкладку перед merge - не потому что заставляем, а потому что находки появляются конкретные и понятные: файл, строка, правило, ссылка на CWE. Это не «у вас плохой код», это «вот эта строка, вот почему».

Что по Dependency Scanning и DAST

Dependency Scanning включили параллельно - шаблон аналогичный:

include:
  - template: Security/Dependency-Scanning.gitlab-ci.yml

Нашёл несколько устаревших PHP-библиотек с известными CVE. Здесь история похожая: обновления давно были доступны, никто просто не смотрел.

DAST (динамический анализ) в рамках этого проекта не включали - он требует работающего staging-окружения в pipeline, что у клиента пока не настроено. Это отдельная задача.

Итог

Встроенный SAST в GitLab Ultimate снижает порог входа до одной строки в .gitlab-ci.yml. Реальная работа - настройка порогов и процесса разбора находок. Без этого либо pipeline постоянно падает, либо на находки никто не смотрит.

Для проектов с историческим кодом имеет смысл начинать с режима «показывать, не блокировать», разобрать накопленное, и только потом ужесточать пороги. Запускать это в рамках аудита безопасности - разумно: сразу понятно, что критично, что терпит, а что вообще ложное срабатывание.

SQL-инъекции в работающем продакшне, о которых команда «наверное догадывалась» - это не гипотетическая угроза. SAST нашёл их за один прогон.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.