Created 2026-03-29

0 stars
Code

26 KiB

Процесс 10: Статический анализ исходного кода

Дата публикации: 29 марта
Время на чтение: ~12 мин
ТГ канал: Про_SEC_со


Расскажу вам выдуманную историю, все совпадения случайны. И, да, друг, если ты думаешь –– это та история которую ты мне рассказал? Нет! Это совпадение!

Один знакомый рассказывал, как его позвали "быстренько глянуть" проект перед релизом. Финтех-стартап, куча микросервисов на Go и Python, команда из 30+ разработчиков. За два года разработки –– только SonarQube. "У нас code review есть и этот SonarQube, зачем нам еще один какой-то анализатор?" –– сказал тимлид на встрече.

Знакомый спросил: "А какая редакция SonarQube?" –– "Community, бесплатная." –– "И профиль безопасности настраивали?" -- "Ну... он же из коробки работает."

Вот тут и кроется ловушка. SonarQube –– отличный инструмент для контроля качества кода: дублирование, цикломатическая сложность, code smells, покрытие тестами. Для этого он и создавался.

Community Edition почти не ищет уязвимости. Серьёзные SAST-правила -- taint analysis, отслеживание пользовательского ввода через цепочку вызовов до SQL-запроса или шаблона, доступны только в платных версиях (Developer, Enterprise). В бесплатной, набор поверхностных проверок: захардкоженный пароль в строке password = "123", пара базовых паттернов. Команда думает, что защищена, а SonarQube просто не видит того, что нужно искать.

Поверхностный taint analysis. Даже платный SonarQube отслеживает поток данных внутри одной функции, может быть через один уровень вызовов. Но он не проследит, как пользовательский ввод из HTTP-хендлера проходит через сервисный слой, маппер, репозиторий и попадает в SQL-запрос через три файла. Специализированные SAST-инструменты (PT Application Inspector, Checkmarx, Semgrep с кастомными правилами) строят граф потоков данных по всему проекту и находят инъекции, которые SonarQube в упор не замечает.

Не понимает фреймворки. SonarQube не знает, что в FastAPI декоратор @app.get("/admin/users") без Depends(get_current_admin) -- это открытый эндпоинт без авторизации. Он не понимает, что в Gin middleware c.Next() вызывается до проверки токена. Для таких вещей нужны правила, написанные людьми, которые знают конкретный фреймворк и его подводные камни.

Знакомый объяснил это тимлиду, поставил Bandit на Python-сервисы и Gosec на Go-сервисы, натравил на репозиторий и ушел пить кофе. Вернулся через двадцать минут, а суммарный отчёт на 1000+ находок. Среди них: SQL-инъекции, много мест с захардкоженными API-ключами (один из которых, ключ к платёжному шлюзу, прямо в коде, в публичном репозитории), 4 обхода авторизации через предсказуемые идентификаторы сессий.

То что было у команды в арсенале, больше для code quality, которому приписали роль SAST-сканера.


Почему стандарт выделяет это в отдельный процесс

ГОСТ Р 56939-2024 отводит статическому анализу исходного кода раздел 5.10 с шестью требованиями и пятью артефактами.

Почему? Потому что слишком часто статический анализ внедряют одним из двух способов, оба провальные.

Способ первый: "Купили и забыли." Компания приобретает лицензию на дорогой коммерческий сканер, ставит его на сервер, один раз запускает. Получает отчёт на 1500 строк. Никто не понимает, что с этим делать. Отчёт ложится в папку "Безопасность" на сетевом диске. Через год повторный аудит, повторный запуск, повторные 1500 строк. Плюс 300 новых. Ничего не исправлено.

Способ второй: "Включили все на максимум." Сканер встроили в CI/CD, включили все правила, поставили блокирующий Quality Gate (хотя о чем это я). Первый же Merge Request падает с 47 ошибками, из которых 44 –– это ложные срабатывания. Через три дня разработчики находят, как обойти проверку. Через неделю кто-то добавляет // nolint на весь файл. Через месяц сканер формально работает, но все его игнорируют.

Процесс 10 по ГОСТу –– это про то, как не наступить ни на одну из этих граблей. Не "поставить тулзу", а выстроить систему, где инструмент реально помогает.


Суть процесса простыми словами

Статический анализ –– это когда программа проверяет другую программу, не запуская ее. Сканер (Semgrep, PT Application Inspector, Checkmarx, PVS-Studio, Svace, Gosec вариантов десятки) разбирает исходный код, строит из него абстрактное синтаксическое дерево и ищет известные опасные паттерны: SQL-инъекции, XSS, утечки памяти, использование небезопасных функций, захардкоженные секреты.

Важно понимать границы. Статический анализатор –– не волшебная палочка. Он не знает, что эндпоинт /admin/users должен быть доступен только администратору. Он не понимает, что бизнес-логика "скидка больше 100%" –– это баг. Для этого нужен человек на экспертизе кода (процесс 9).

Зато SAST делает то, чего человек физически не может: проверяет весь код, каждый коммит, за секунды, по сотням правил одновременно. И не устает к пятнице.


Требование 5.10.2.1: Регламент –– договорённость, а не бумажка

Разработать регламент проведения статического анализа исходного кода ПО.

Регламент (артефакт 5.10.3.1) –– самый важный документ процесса. Без него сканер превращается в генератор шума, с ним в рабочий инструмент.

ГОСТ требует, чтобы в регламенте были:

  • роли и обязанности (кто настраивает, кто разбирает находки, кто верифицирует разметку);
  • критерии выбора инструментов;
  • критерии выбора модулей для анализа;
  • правила обработки срабатываний;
  • типы и критичность ошибок, подлежащих устранению, и приоритеты;
  • периодичность проведения анализа или события-триггеры;
  • критерии пересмотра конфигурации.

Звучит как бюрократия? Расскажу, зачем каждый пункт нужен на практике.

Представим в одной команде три AppSec'а независимо друг от друга размечали одну и ту же находку: один как False Positive ("это тестовый код"), второй как Won't Fix ("это legacy, не трогаем"), третий завел баг и создал issue на исправление разработчику. Никто не знал, кто прав. Потому что не было правил обработки срабатываний. Не было ответа на простой вопрос: "Кто принимает решение по находке, и как это решение фиксируется?"

Регламент –– это ответ на все такие вопросы до того, как они зададутся в панике.


Требование 5.10.2.2: Инструменты по языку, а не "один на всех"

Определить инструменты статического анализа для каждого используемого в ПО языка программирования.

ГОСТ формулирует это просто, но за простотой принципиальная мысль. Нет универсального сканера. Для C/C++ отлично работает PVS-Studio или Svace. Для Python –– Bandit и Semgrep. Для Go –– Gosec и staticcheck. Для Java –– SpotBugs и Checkmarx врозможно и другие.

Артефакт 5.10.3.2 –– перечень инструментов который должен содержать наименования, версии и соответствие языкам. Не "мы используем SonarQube", а конкретно:

| Язык        | Инструмент                      | Версия   | Назначение                  |
|-------------|---------------------------------|----------|-----------------------------|
| Go          | Gosec                           | 2.19.0   | Поиск уязвимостей           |
| Go          | staticcheck                     | 2024.1   | Качество кода + безопасность|
| Python      | Bandit                          | 1.7.7    | Поиск уязвимостей           |
| TypeScript  | ESLint + eslint-plugin-security | 8.52.0   | Безопасность фронтенда      |
| Все языки   | gitleaks                        | 8.18.1   | Поиск секретов в коде       |

Почему версии важны? Потому что обновление сканера –– это тоже событие, при котором нужно пересматривать конфигурацию (требование 5.10.2.5). Новая версия может добавить правила, изменить поведение существующих или исправить ложные срабатывания. Без фиксации версий вы не сможете объяснить аудитору, почему отчёт за январь показывает 50 находок, а за март 120 при том же коде.


Требование 5.10.2.3: Настройка –– сердце всего процесса

Определить конфигурацию и параметры настройки инструментов статического анализа.

Вот здесь начинается настоящая работа. Сканер "из коробки" –– это как детектор металла на максимальной чувствительности: он пищит на каждый гвоздь в полу. Чтобы находить оружие, а не фурнитуру, нужна калибровка.

Конфигурация (артефакт 5.10.3.3) должна явно задавать:

Что считается блокером, а что информацией. Захардкоженный API-ключ в коде –– Blocker, сборка не пройдет. Использование fmt.Sprintf вместо fmt.Fprintf в нелогируемом месте –– Info, разработчик увидит, но релиз не остановится.

Что мы вообще не проверяем. Сгенерированный код (protobuf-стабы, swagger-клиенты), вендорные зависимости (для них есть процесс 16 о композиционном анализе), тестовые моки. Если не исключить эти области, 80% отчёта будет мусором.

Как разработчик легитимно отмечает ложное срабатывание. Это критично. Если процедура неудобная, то разработчик найдёт обходной путь. Если её нет находки будут копиться вечно, и через полгода сканер выдаёт 2000 "открытых" issues, из которых 1800 ложные, а 200 реальных утонули в шуме.

Пример: Quality Gate для проекта

Quality Gate (критерии прохождения сборки):
  - 0 новых уязвимостей уровня Critical и High
  - 0 секретов в коде (пароли, токены, ключи)
  - Новые Medium/Low автоматически заводятся в трекер
    в бэклог технического долга со сроком устранения

Разметка False Positive:
  - Разработчик размечает в интерфейсе сканера
    с обязательным текстовым обоснованием
  - Security Champion верифицирует разметку
    в течение 2 рабочих дней
  - Без верификации разметка не засчитывается

Требование 5.10.2.4: Сканирование как часть ежедневной работы

Проводить статический анализ с использованием инструментов статического анализа с регистрацией всех предупреждений.

Ключевое слово здесь –– это "регистрация". Недостаточно просто запустить сканер. Каждое срабатывание должно быть зафиксировано: найдено, кем обработано, какое решение принято. Это артефакт 5.10.3.4, отчёты с результатами разметки.

На практике это выглядит так. Разработчик открывает Merge Request. В пайплайне автоматически стартует инкрементальное сканирование, проверяется только изменённый код, не весь проект. Если сканер нашёл Critical -- пайплайн падает, MR не может быть смержен. Разработчик видит конкретное место в коде, конкретное правило и рекомендацию по исправлению. Исправляет, пушит снова. Повторный анализ –– чисто. Только после этого код идёт на экспертизу человеку (процесс 9).

Вспомните ту историю из начала статьи. Если бы в том финтех-стартапе сканер стоял в CI/CD с первого дня то SQL-инъекции не дожили бы до продакшена. Каждая из них была бы поймана в момент создания MR, когда разработчик ещё помнит контекст и может исправить за пять минут. А не через два года, когда этот разработчик уже уволился, а код оброс зависимостями.


Требование 5.10.2.5: Настройки не навсегда

Осуществлять пересмотр конфигурации и параметров настройки инструментов статического анализа при выполнении установленных событий.

Конфигурация SAST –– живой организм. ГОСТ прямо перечисляет события, при которых её нужно пересматривать: изменения в правилах сборки, смена версии анализатора, получение информации о новых уязвимостях.

На практике есть ещё одно неочевидное событие: накопление ложных срабатываний. Если какое-то правило стабильно даёт 95% false positives, его нужно либо донастроить, либо понизить критичность, либо отключить. Иначе развивается то, что можно назвать "сканерная слепота": разработчики перестают читать отчёт, потому что привыкли, что там мусор. А в этом мусоре однажды окажется реальная уязвимость –– и её никто не заметит.

К сожалению, да. Один из самых надёжных способов убить процесс SAST –– это не отключить шумное правило, а оставить его "на всякий случай".


Требование 5.10.2.6: Повторный анализ и замыкание цикла

Осуществлять повторный статический анализ ПО после устранения ранее выявленных ошибок; внесения изменений; изменения версий компиляторов, сред выполнения, обновлений инструментов.

Это требование закрывает цикл. Нашли уязвимость –– исправили –– проверили что исправление не сломало ничего нового. Обновили компилятор, сразу перепроверили, потому что новый компилятор может по-другому оптимизировать код, и то, что раньше было безопасно, теперь нет.

Звучит очевидно, но на практике повторный анализ часто "забывается". Исправили баг, закрыли тикет, поехали дальше. А через месяц выясняется, что фикс одной SQL-инъекции создал другую, в соседней ветке условия.


Артефакты: что реально нужно показать

Давайте без иллюзий: аудитор не хочет читать PDF на тысячу страниц со списком срабатываний. Ему нужно увидеть, что процесс работает. Вот что для этого нужно:

Регламент (5.10.3.1) –– это документ, из которого понятны правила игры. Роли, критерии, приоритеты. Не шаблон из интернета, а документ, который отражает вашу (вашего предприятия/компании) реальность.

Перечень инструментов (5.10.3.2) –– это таблица "язык, сканер, версия". Если проект на трёх языках, а сканер один и он не поддерживает один из языков –– это несоответствие.

Конфигурации (5.10.3.3, 5.10.3.5) –– это файлы настроек или скриншоты профилей в интерфейсе сканера. Доказательство того, что вы управляете правилами осознанно, а не оставили всё по-умолчанию.

Отчёты и разметка (5.10.3.4) –– это дашборд, из которого видна прослеживаемость (Оркестратор потенциальными уязвимостями). Вот находка. Вот она размечена, либо как False Positive с обоснованием, либо на неё заведён тикет. Вот тикет закрыт, вот повторное сканирование подтверждает исправление. Цепочка от обнаружения до устранения, без разрывов.


Как это работает вместе

Статический анализ, не одиночный солдат. Он часть эшелонированной обороны.

Процесс 8 (правила кодирования) задаёт правила: "не используй конкатенацию в SQL", "не храни секреты в коде". Процесс 10 превращает эти правила в автоматические проверки. По сути, SAST –– это правила кодирования.

Процесс 9 (экспертиза кода) работает в тандеме. Анализатор ловит паттерны: инъекции; небезопасные функции; утечки. Человек на ревью ловит логику: обходы авторизации; ошибки в бизнес-правилах; архитектурные просчёты. Вместе они закрывают то, что поодиночке не закроет ни один из подходов.

Есть ещё один неочевидный эффект. Когда SAST стоит в пайплайне, нагрузка на ревьюера снижается. Ему не нужно вручную искать захардкоженные пароли или проверять, параметризован ли каждый SQL-запрос –– это уже сделал статический анализатор, наш автоматизированный процесс. Ревьюер может потратить своё время на то, что действительно требует человеческого мозга: понять замысел автора, оценить архитектурное решение, задать неудобный вопрос "а что будет, если пользователь вызовет этот метод дважды одновременно?".

Вернёмся еще раз к той истории с финтех-стартапом. После внедрения специализированных SAST-инструментов команда потратила три спринта на разбор технического долга, тысячи находок не исправляются за день. SonarQube, кстати, никуда не делся, он остался для контроля качества кода, дублирования, покрытия тестами. Просто перестал быть "единственным рубежом обороны". Рядом с ним встали Bandit, Gosec, gitleaks, каждый на своём участке.

Через полгода количество уязвимостей в продакшене упало на порядок. Тимлид, который когда-то говорил "зачем нам ещё какой-то анализатор", теперь не представлял, как без них работать.