GitLab 11.4 SAST в реальном пайплайне: ложноположительные и настоящие SQL-инъекции
Запустили встроенный SAST из GitLab 11.4 на внутреннем проекте. Половина находок - мусор, но несколько реальных SQL-инъекций в legacy-коде нашёл честно.
GitLab 11.4 (октябрь 2018) - встроенное SAST и DAST сканирование доступно в пайплайнах без внешних инструментов, результаты видны прямо в merge request
GitLab 11.4 вышел в середине октября и главное, что мы там заметили, - встроенное сканирование безопасности теперь работает без плясок с бубном. SAST (статический анализ кода) и DAST (динамическое сканирование развёрнутого приложения) доступны прямо из коробки: добавляешь шаблон в .gitlab-ci.yml, и в merge request появляется отдельная вкладка с находками. Раньше это было частью Auto DevOps, мы его трогали в 11.0 и сразу отключали Security стадии как лишний шум. Теперь решили взглянуть серьёзнее.
Взяли один из внутренних проектов - PHP-сервис, которому лет пять, писался под давлением дедлайнов разными людьми. Именно тот тип legacy, к которому стараешься не прикасаться без причины. Причина нашлась.
Как подключается
В .gitlab-ci.yml добавляется один include:
include:
- template: SAST.gitlab-ci.yml
GitLab подтягивает шаблон, который добавляет в пайплайн стадию sast. Для PHP это запускает контейнер с phpcs-security-audit и bandit-подобными правилами. Для Python - bandit. Для Node - eslint с security-плагином. Язык определяется автоматически по содержимому репозитория.
Никакого внешнего сервиса, никаких API-ключей - Docker-образы GitLab тянет из своего registry. На managed-инфраструктуре это удобно: пайплайн самодостаточен, сетевых зависимостей от внешних сканеров нет.
DAST подключается отдельно и требует работающего окружения - ему нужен URL задеплоенного приложения. Мы его пока оставили на потом, смотрели только SAST.
Что нашёл
Первый запуск выдал список находок на несколько экранов. Смотрели вместе с разработчиком, который этот код лучше других знает.
Ложноположительные составили примерно половину. Типичные паттерны:
eval()в шаблонизаторе. Сканер помечает любойevalкак критическую уязвимость. В нашем случае это внутренний шаблонизатор, которыйeval-ит заранее скомпилированные PHP-шаблоны из кеша. Входные данные туда не попадают никогда - это чисто внутренняя генерация кода. Для сканера это неотличимо отeval($_POST['code']), поэтому он честно пишет CRITICAL.- Устаревшие MD5-хеши для некритичных данных. Проект хеширует MD5-ом идентификаторы сессий кеша - не пароли, не токены, просто ключи для memcached. Сканер видит
md5()и сигнализирует об использовании слабого алгоритма. Технически верно, практически - не угроза. - Флаги в регулярках.
preg_replaceс модификатором/e- deprecated ещё в PHP 5.5, давно не используется в проекте, но в одном старом вспомогательном файле осталась строка в комментарии. Сканер нашёл строку в комментарии.
Это раздражает, потому что размывает внимание. Когда в отчёте 30 CRITICAL и половина - мусор, начинаешь относиться ко всему списку небрежно. Это классическая проблема precision/recall у инструментов статического анализа, и в текущем виде GitLab её не решает.
Реальные находки - и вот тут стало интересно. Сканер нашёл несколько мест с конкатенацией пользовательского ввода прямо в SQL-строку:
// примерно такое
$query = "SELECT * FROM orders WHERE user_id = " . $_GET['uid'];
$result = mysql_query($query, $conn);
Таких мест оказалось три. Все в одном модуле работы с отчётами - написан лет пять назад, использует mysql_query (deprecated в PHP 5.5, убран в PHP 7.0 - проект до сих пор на PHP 5.6). Ввод из GET-параметров, никакой экранизации, прямая конкатенация.
Это классическая SQL-инъекция. Модуль отчётов доступен только авторизованным пользователям, но это не делает уязвимость гипотетической - залогиненный пользователь тоже может быть атакующим. Или сессия угнана.
Что с этим делать
По реальным находкам завели задачи. Переписать три запроса на PDO с prepared statements - работы на несколько часов, но откладывалось годами потому что «работает, не трогай». Сканер дал конкретные номера строк и файлы - это снижает порог входа для фикса.
По ложноположительным - GitLab позволяет помечать находки как false positive прямо в интерфейсе merge request. Это скрывает их из активных предупреждений, но сохраняет в истории. Удобно, но ручная работа по первичной разметке всё равно нужна.
DAST планируем подключить к staging-окружению отдельно - там свои нюансы с аутентификацией и авторизацией при сканировании.
Вывод по первому запуску
Инструмент оправдывает себя. Не потому что нашёл всё и не создал шума - шума как раз много. А потому что нашёл реальные SQL-инъекции в коде, до которого руки не доходили годами. Статический анализ в пайплайне снижает стоимость «заглянуть в безопасность»: не нужен отдельный инструмент, отдельный процесс, аудитор раз в год. Просто часть CI, которая молчит когда всё хорошо и кричит когда нашла что-то.
Соотношение ложноположительных к реальным - вопрос зрелости правил и конфигурации. Хотелось бы видеть возможность настраивать severity-порог и исключать паттерны без ручной разметки каждой находки. Посмотрим как это будет развиваться в следующих версиях.
Для проектов с legacy-кодом, где история написания размыта, - запускать стоит. Больно, зато честно.