(S)SDLC, или Как сделать разработку безопаснее. Часть 2

(S)SDLC, или Как сделать разработку безопаснее. Часть 2
– Наташ, а Наташ? Мы там, это… SAST внедрили.
– Мы там всё уронили, Наташ. Вообще, всё!!!
– Пайплайны стоят, очередь забита…
– Ни одной сборки не прошло! Вставай, Натаааш!




Вот так примерно можно проснуться на следующее утро после внедрения в разработку статического анализа кода. Если заранее не подготовиться к этой увлекательной процедуре.
А можно получить совсем другой, намного более позитивный и полезный для разработки и бизнеса результат. Если учесть при внедрении ряд технических нюансов SAST-анализа и вовремя подстелить соломку. Об этих нюансах сегодня и поговорим!

В предыдущей статье из серии о внедрении SAST мы уже говорили, что в хорошем SAST-туле реализовываются сложные алгоритмы межпроцедурного анализа потока данных. Их экспоненциальная природа приводит к тому, что SAST может работать довольно долго, потреблять много ресурсов и давать ложные срабатывания. Разберемся, что со всем этим делать.

Скорость и ресурсы


Начнем с двух вопросов, которые мы обещали разобрать:

  • Подключение SAST как SonarQube — в чем сложность?
  • SAST работает долго — как настроить DevSecOps?

В большинстве случаев SAST внедряется в компании не на пустом месте. Уже имеется настроенный CI/CD с удобными для разработчиков триггерами и шагами. И наверняка применяются какие-то статические анализаторы: checkstyle, линтер, возможно, даже SonarQube.

Исходя из уже имеющегося опыта построения CI/CD-процесса, специалисты по автоматизации ожидают от нового инструмента следующих возможностей:

  1. Новый тул будет работать быстро (так же, как линтер и SonarQube).
  2. Запускать его можно будет на каждый пуш.
  3. Можно будет показать, кто и в каком коммите внес уязвимость.
  4. По результатам работы тула можно настроить Quality Gate.

Начинаем пробовать (например, в рамках пилота SAST-тула). Как мы советовали в первой части, выберем критичную большую кодовую базу и на ней проверим автоматизацию и интеграцию SAST. Либо с помощью плагина к CI/CD-серверу, либо с помощью вызовов API добавляем новый шаг под названием SAST по аналогии с другими шагами. На следующий день понимаем, что все упало: пайплайны стоят, очередь забита, ни одной сборки не прошло. Что произошло?

К сожалению, при использовании хорошего SAST-тула, который находит сложные уязвимости, придется столкнуться со следующей реальностью:

  1. На внушительной кодовой базе тул может работать долго и будет потреблять много ресурсов.
  2. Из-за этого оказывается неэффективно запускать его на каждый пуш, особенно, если таких пушей много.
  3. Не всегда понятно, как определить коммит и автора уязвимости: изменение кода в одном месте может привести к появлению уязвимости совсем в другом месте (в первой части мы говорили, что важная составляющая SAST — межпроцедурный анализ потока данных).
  4. Есть ложные срабатывания, поэтому без дополнительной настройки Quality Gate будет всегда падать.

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

  • ~200 разработчиков
  • 14 команд разработки
  • 232 кодовых базы (>32 млн LOC)

и зоопарк технологий. Языки: Java, JavaScript, PHP, C#, Kotlin, Objective-C, Swift, SAP ABAP. В разных командах использовались разные тулы для обеспечения процесса разработки: Jira, TFS, gitlab, Jenkins.

На всем этом объеме надо было построить процесс безопасной разработки: внедрить SAST, автоматизировать, запустить процесс.

Для начала, чтобы быть более конкретным, приведу немного статистики. Посмотрите на таблицу ниже.



В каждой строке указаны язык, объем кодовой базы, время работы SAST-тула, количество уязвимостей — общее и в разбивке по уровням критичности. Из таблицы видно, что большая кодовая база может сканироваться несколько часов, и количество уязвимостей крайне внушительно. Посмотрим на строки 2 и 3: объем кода в строке 3 больше, однако время анализа — меньше. В строке 4 кода практически столько же, сколько в строке 3, а время анализа сильно меньше. В последней строке кода сильно больше, чем в первой, а время анализа — меньше.

Почему так? К сожалению, время анализа (и потребляемые ресурсы) зависят не только от объема кода. Сильно влияют и особенности конкретного языка, и используемые языковые конструкции. Очень запутанный код с множеством ветвлений и запутанным потоком данных может анализироваться значительно дольше, чем простой код того же объема на том же языке. На одних и тех же кодовых базах разные анализаторы будут показывать разное время.

Что с этим делать? Важно посмотреть время анализа всей кодовой базы и объемы потребляемых ресурсов именно на вашем коде. Не нужно использовать экстраполяции — это может быть весьма неточно. Далее, когда вы понимаете время анализа и количество ресурсов, которое можете выделить на SAST, становится ясно, по каким триггерам нужно запускать анализ. Почти всегда можно найти триггер, который будет удобен разработчикам и при этом не будет тормозить CI/CD. Нужно понять, какие действия совершать по окончании анализа: высылать отчеты на почту или использовать какие-то другие оповещения. Примеры приведу чуть позже.

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



Тут нужно упомянуть инкрементальный анализ. В инкрементальном анализе при пересканировании кодовой базы будет проверяться только измененный код. Казалось бы, — вот оно, решение проблемы. Однако опять отошлю читателя к основе SAST — межпроцедурному анализу потока данных. При изменении кода в одном месте, может появиться уязвимость в другом месте программы. Чаще всего при инкрементальном сканировании будут пропускаться уязвимости. Поэтому эту фичу надо использовать очень осторожно.

Примеры внедрения


Все примеры будут из кейса, который я упоминал выше. Для начала приведем простой пример, достаточно стандартный для внедрения SAST.



Дано: gitlab для работы с кодом и построения CI/CD, разработка по gitflow. Кода довольно много, сканирование идет 2-3 часа. Решение — запускать анализ dev-ветки каждую ночь при наличии в ней изменений. С одной стороны, код будет проверяться довольно оперативно: как только фича вливается в dev, на следующий день разработчик видит результаты сканирования и может внести правки, если надо. Оповещения по результатам сканирования при наличии новых уязвимостей приходят на почту пользователям с уровнем maintainer в gitlab. Результаты сканирования можно смотреть в интерфейсе SAST, уровни доступа к результатам автоматически настраиваются в соответствии с уровнями доступа к репозиторию в gitlab.



В одной из команд было решено усовершенствовать предыдущее решение. Помимо еженочных сканирований основной ветки, на каждый push проходило инкрементальное сканирование измененного кода. При наличии новых уязвимостей автору пуша приходило оповещение. Таким образом примерно 30 процентов уязвимостей отсекаются сразу, остальные можно будет увидеть при полном сканировании на следующий день после вливания ветки.



А теперь опишу более сложный пример. В нескольких командах используется другая система работы с ветками. Все фича-ветки довольно долгое время живут без вливания в dev. Ветка проходит все стадии ревью и тестирования и только перед релизом вливается в dev. Объем кода на Java составлял несколько миллионов строк, над ним трудится несколько десятков разработчиков с десятками коммитов в день. CI/CD основан на Jenkins, работа с кодом при этом ведется в gitlab.

Понятно, что сканировать каждый пуш было бы нереально. И процесс идет долго, и потребовалось бы очень много ресурсов. Но и предыдущее решение не очень подходит: при сканировании dev-ветки информация о новых уязвимостях появлялась бы только перед релизом.

Компромисс был найден по результатам плотного погружения в жизненный цикл задачи на разработку. Оказалось, что хороший момент для проверки кода на уязвимости — это момент, когда разработчик переводит задачу в Jira из статуса “в работе” в статус “ревью”. Таких изменений статусов для каждой задачи немного, как и в целом подобных изменений в день. Изменение статуса происходит сразу после того, как разработчик закончил работать над задачей.

Процесс был автоматизирован следующим образом:

  1. Интеграция Jira и Jenkins. При изменении статуса задачи в Jira автоматически запускался пайплайн проверки кода этой задачи в Jenkins. Ветка определялась по идентификатору задачи. Помимо этого, анализ ветки можно было запустить вручную из Jenkins, а также с помощью управляющего комментария к задаче в Jira.
  2. Интеграция gitlab и Jenkins. Запустить сканирование также можно было комментарием в запросе на слияние в gitlab. Отчет при наличии новых уязвимостей приходил на почту пользователям с уровнем developer и выше. Новые уязвимости появлялись в виде комментариев к коду в запросе на слиянии в gitlab. Уровень уязвимостей, которые должны попадать в gitlab, а также уровень доступа пользователей, которым нужно было направлять оповещения, были настраиваемыми параметрами.
  3. В самом SAST проводилось два сканирования: сканирование основной ветки, от которой ушла фича-ветка, и сканирование фича-ветки. Это нужно было для того, чтобы подсветить новые уязвимости, появившиеся по результатам выполнения задачи.

Таким образом, всегда можно найти удачное решение. Нужно учитывать особенности SAST, но не бояться их: если один раз выстроить правильную схему его использования, потом можно максимально автоматизированно находить сложные уязвимости.

В описании кейса я упомянул ABAP. В SAP также можно автоматизировать проверку кода. Например, можно запланировать выполнение SAP-программы, которая выгружает код из SAP-системы на внешний сервер и запускает сканирование кода. Результаты проверки можно использовать при переносе запросов между ландшафтами.

Результаты анализа


Первый этап мы прошли — разобрались, в какой момент запускать сканирование так, чтобы это было и удобно, и эффективно. Теперь посмотрим на результаты.

Рассмотрим следующие два вопроса из первой части нашей серии статей:

  • SAST дает ложные срабатывания — как настроить Quality Gate?
  • И без ложных срабатываний в отчете несколько тысяч уязвимостей — что с ними делать?

Действительно, прогнав первый раз анализ на большой кодовой базе, вы увидите очень много уязвимостей (тысячи, возможно, даже десятки тысяч). И если присмотреться к ним, можно увидеть ложные срабатывания. Сходу не очень понятно, что с этим делать. Исправлять — месяцы работы. И внедрять не очень удобно — получается, разработчики будут постоянно получать уведомления о несуществующих уязвимостях?

Эти вопросы решаемы: посмотрим, какие функции SAST помогут в такой ситуации.

Начнем с фильтрации при запуске сканирования.

  1. Фильтрация по файлам и директориям. Можно исключить неиспользуемый код, ненужные библиотеки, тестовый код.
  2. Фильтрация по уязвимостям. Какие-то уязвимости могут быть нерелевантны вашему случаю — их можно полностью исключить из поиска еще при старте сканирования.
  3. Исключение библиотек. Некоторые инструменты сами определяют, где в коде библиотеки, одной галочкой их можно исключить из анализа.

Все три метода надо использовать очень аккуратно. Может измениться структура вашего кода, может обновиться статический анализатор, и настройки перестанут быть актуальными, а уязвимости будут пропускаться. С исключениями библиотек также надо быть осторожным — часто именно в них могут присутствовать критические уязвимости.

Результаты сканирования тоже можно фильтровать. Обычно есть уровни критичности найденных уязвимостей — 3 или 4, в зависимости от инструмента. Некоторые тулы показывают уровень уверенности в истинности срабатывания, это очень полезный фильтр.

Важно здесь упомянуть о процедуре обработки результатов. Обычно в интерфейсе инструмента можно поменять критичность срабатывания, поменять статус (например, пометить как ложное), объяснить свои действия комментарием. Собственно, эта процедура необходима, особенно после первого сканирования. Самое важное: в инструменте должно быть реализовано отслеживание уязвимостей между сканированиями в рамках одного проекта. Оно должно быть устойчиво к стандартным изменениям кода (сдвиг строк и т.п.). Благодаря этой функции полную обработку результатов нужно сделать только один раз. Например, пометили уязвимость как ложную — в последующих сканированиях она будет автоматически помечена как ложная.

Картина становится лучше. Однако остается проблема: уязвимостей все равно много, возможно, несколько тысяч, что делать в таком случае?

Главная рекомендация в подобной ситуации — не ждать исправления всех уязвимостей и сразу внедрять процесс. При этом пока не исправлены все старые уязвимости, в непрерывном процессе разработки исправлять только новые. Тут важным аспектом является наличие функции определения новых уязвимостей в инструменте, обычно эта функция основана на механизме отслеживания уязвимостей между сканированиями.

Получается следующий процесс. Сразу внедряем SAST в CI/CD, постоянно смотрим на новые уязвимости и их исправляем (или помечаем как ложные, и дальше они не возникают) — таким образом мы не допускаем появления новых уязвимостей. Постепенно в рамках технического долга обрабатываем результаты первого сканирования и исправляем старые уязвимости. При разборе старых уязвимостей можно пользоваться различными методами приоритизации: по критичности, по уровню уверенности инструмента в истинности срабатывания. Можно начинать сначала с собственного кода, а потом уже разбирать библиотеки. Можно начинать с уязвимостей, у которых меньше всего вхождений.

Возвращаясь к нашему кейсу внедрения, о котором я говорил выше: используя такой процесс, в одной из команд примерно с 0.5 млн строк кода, 5-ю разработчиками и одним специалистом ИБ все старые уязвимости были разобраны и исправлены за 5 месяцев (параллельно с выполнением обычных задач и исправлением 5-10 новых уязвимостей в день).

Продолжение следует


SAST обладает рядом технических особенностей, которые, с одной стороны, делают его необходимым для использования в процессе разработки, а с другой, усложняют его внедрение. Но, как мы показали в этой статье, все вопросы можно решить, если знать об этих особенностях и следовать рекомендациям. В заключительной части серии мы поговорим про организационную составляющую построения SSDLC.

P.S. Если вы сталкивались с другими техническими проблемами при внедрении SAST и готовы поделиться своим опытом их решения, пожалуйста, пишите в комментариях. Также будем рады ответить на ваши вопросы по технической стороне внедрения SAST.
Alt text
Комментарии для сайта Cackle