SCA на языке безопасника
За последнее время внутри компании у нас появилось несколько энтузиастов, заинтересованных в DevSecOps по совершенно разным причинам. Кому-то задал неудобный вопрос заказчик, кто-то решил встать на путь саморазвития, а у кого-то близкий человек — AppSec, и хочется иметь возможность поддерживать разговор о работе.
В этой статье хотелось бы рассказать простым «ИБшным» языком про ядро процесса безопасной разработки для тех, кто сталкивается с этим впервые или что-то где-то слышал, но не может увязать это в стройную картину. Потому что на самом деле это максимально близкая вещь к тому, что вы или ваши коллеги уже знаете из своей повседневной работы, даже если ни разу не работали в разработке и не касались исходного кода.
Теперь можно будет попробовать заговорить на одном языке с теми заказчиками, которые, помимо того, что хотят выстроить у себя ИБ-процессы, являются еще и полноценными разработчиками собственного ПО. Поехали!
Что такое SCA?
Что стоит за этой аббревиатурой и как ее у нас адаптируют? В оригинале это Software composition analysis, чаще всего SCA у нас звучит как Композиционный анализ кода. Редко когда говорят композитный, что не совсем верно. И также есть адаптация от вендора отечественного SCA — компонентный. Последнее, как мне кажется, наибольшим образом отражает суть.
В чем же суть? Прежде всего, SCA — это такой же процесс, как, например, управление активами или расследование инцидентов. Не какое-то разовое действие, а постоянно повторяющиеся проверки, нацеленные на повышение уровня безопасности продукта или приложения.
В ходе такого процесса происходит декомпозиция продукта с целью определить зависимости, лицензии и уязвимости, а также возможную совместимость лицензий (например, если вы затянули к себе зависимость с GPL, теперь якобы нужно открыть исходный код продукта) используемых компонентов. Если упростить, результатом регулярных проверок станет подсветка слабых мест как в исходном коде, так и в затянутом в проект OpenSource, и в результате владелец получает знание о покрытии компонентов проверками: артефакты, необходимые для принятия дальнейших решений по работе с установленными рисками.
Для кого это и зачем?
Есть три причины, по которым требуется внедрение SCA-процесса в компанию (конечно, при том условии, что компания является разработчиком ПО):
· Лицензирование — в некоторых случаях нужно установить, можно ли вообще продавать продукт собственной разработки, если в нем, к примеру, есть компонент «не для коммерческого использования».
· Сертификация — регулятор регламентирует требования к безопасности исходного кода, без проверки этих требований нельзя сказать, что вашему ПО можно поверить «на слово».
· Безопасность — даже если вы не продаете свой продукт и не хотите получить сертификат, будет, мягко говоря, неприятно, если ваше детище взломают или если ваше приложение станет причиной взлома ваших пользователей.
Приложение при этом может быть каким угодно — будь то мобильные приложения, серьезные продукты-СЗИ и даже игры. Всё, что разработано у вас, заказчика или даже ваш пет-проект — по большому счету, все это нуждается в проверке и своевременной реакции.
SCA как процесс
Исходный код и бинарные сборки
Начнем с того, с чем работают люди, когда речь идет про SCA. Здесь все проще простого — исходный код, бинари. То, что написано человеком (или с помощью Copilot) подлежит проверке на наличие уязвимых зависимостей.
Да и в целом, результатом может быть и сам граф зависимостей — для понимания того, какой в продукте процент закрытого кода и открытого, сколько в будущей сборке будет уязвимых точек. Некоторые специалисты сразу отталкиваются от поверхности атаки — возможности исследователя или хакера взломать продукт и нанести вред. Для того, чтобы определиться со стратегией и первыми шагами, нужно понять, как расставить приоритеты.
Как выбрать, что проверять?
На самом деле мнений тут несколько. И в SDL-сообществе, и на различных публичных выступлениях картина вырисовывается не совсем однозначная. Скорее получается так, что есть какой-то общепринятый шаблон действий и есть ряд факторов, из-за которых стратегия может быть скорректирована. Но если внедрение процесса происходит впервые, начинать лучше с очевидного и простого.
В составе исходного кода в части зависимостей могут быть OpenSource библиотеки и какие-либо проприетарные. Вторые проверить сложнее, вместо проверки непосредственно кода придется довериться вендору или базам знаний об уязвимостях, по сути получая «кота в мешке» без возможности подтвердить, что найденные уязвимости — это всё, что поджидает в закрытом коде вендора.
Поэтому зачастую приоритет отдается открытому ПО. Есть также и вторая причина — про него будет знать злоумышленник и, если он захочет ваш продукт взломать, он начнет как раз-таки с известных уязвимостей открытых библиотек.
Вторым шагом проверки чаще всего называют контейнерные сборки.
Если все эти шаги уже пройдены и процесс налажен, то можно позволить себе потратить время на проверку ПО-IDE, в котором работает разработка, проверить ОС, на которой будет установлен продукт — есть вероятность, что ваш продукт может быть очень хорош и безопасен, но нарушить работу вашей игры или системы можно будет, взломав саму ОС.
Зависимости. Важные нюансы
После того как скоуп работ определен, начинается сбор информации о зависимостях. Все импорты, все дополнительные библиотеки различными способами ищутся в исходном коде.
Какие есть способы? Если перечислить их от максимально не автоматизированных к автоматизированным, то это:
· максимально ручная работа с попыткой запустить продукт на «голой» системе и поиск пакетов зависимостей вручную;
· ручной поиск и пробив по базам знаний;
· использование OpenSource или вендорских продуктов.
В чем можно искать зависимости?
· непосредственно исходный код, например, файл «requirements.txt» для питона;
· регулярки по ключевым словам import, scope и т.д.
· репозитории проектов — для вышеперечисленного;
· сборки готовых продуктов;
· контейнеры и виртуальные машины с готовым продуктом.
Основной вопрос в том, как зацепить все зависимости? И как не разрушить свою работу своими собственными руками. Например, полезный совет экспертов — обязательно фиксировать версии библиотек и их зависимостей, поскольку результат SCA-анализа потеряет свою актуальность, если вы не будете управлять обновлениями и не будете понимать, какие версии библиотек используются в продукте в конкретный момент времени.
Здесь стоит отметить, что вышеописанный процесс максимально похож на уже знакомый безопасникам процесс поиска и управления уязвимостями с тем только отличием, что тут — зависимости и библиотеки, а там — программное обеспечение. Точно так же эти процессы будут схожи в дальнейшем, когда мы перейдем к митигации и исправлениям.
Так же, как сканер уязвимостей проверяет файлы, их содержимое для поиска наименований и версий ПО на сервере или рабочей станции, инструмент SCA-анализа проверяет код и ищет в нем наименования и версии библиотек. Куда эта информация попадает дальше?
SBOM и прочие бомы
Давайте познакомимся: SBOM (Software Bill of Materials) — это машиночитаемый формат, в котором перечислены библиотеки и их версии. Такой документ можно передать исследователям, которые займутся или поиском известных уязвимостей для этого ПО, или протестируют конкретную версию вручную.
Информация о зависимостях тоже находится здесь, таким образом, когда обнаружится уязвимость в каком-то из компонентов, можно будет вернуться на шаг назад и понять, как именно этот компонент попадает в продукт.
Используя такой документ, можно выстроить предполагаемую поверхность атаки, понять, где в итоговом продукте слабые места.
Еще существует MLBOM для моделей искусственного интеллекта, но это просто для общего развития.
Что еще собираем?
Помимо того, что мы можем найти в открытом коде, подсмотрев у других исследователей результаты анализа, можно протестировать как открытый, так и закрытый код с той же целью. Нередко результаты статического и динамического анализа кода идут в общую базу данных уязвимостей конкретного продукта для последующей обработки.
После того как зависимости установлены, а уязвимости обнаружены, они отправляются в системы, которые автоматизирует SCA-процесс. Такие системы позволяют настроить необходимые политики отслеживания рисков — фризы обновлений и разработки, непосредственные обновления компонентов и уведомления ответственных за процесс и патчинг лиц.
Мы нашли уязвимости. Что дальше?
Здесь такая же аналогия с процессом управления уязвимости — все, как в вашей привычной работе — нужно ознакомиться с рисками и принять решение о том, что делать дальше.
Чья это ответственность?
Чаще всего такие проблемы решают сами разработчики при участии коллег из AppSec, которые помогут выбрать правильную стратегию.
Как правильно устранить найденные уязвимости, приоритизировать их и провести первоначальный триаж — мнения разнятся, как и на этапе выбора области проверки. Объединяет их то, что эксперты отталкиваются от видов зависимостей, найденных в продукте.
Виды зависимостей
Зависимости бывают прямые и непрямые (еще их называют директивные и транзитивные). Что это значит?
Например, у вас код на python, в котором реализованы некоторые API-запросы, и вы решили использовать библиотеку requests. Это директивная зависимость. Она, в свою очередь, тянет за собой как одну из своих зависимостей библиотеку urllib — это уже с точки зрения кода зависимость транзитивная.
В транзитивных зависимостях есть больший шанс того, что в них найдутся уязвимости. Возможно, это обусловлено тем, что в крупные проекты контрибьютят больше и чаще, проверяют их тоже чаще и, как следствие, больше находят и устраняют уязвимостей. Или на самом деле, просто статистически: на одну директивную зависимость приходится несколько транзитивных, так что и вероятность обнаружения уязвимостей выше.
Но также транзитивные зависимости чаще выходят на поверхность атаки, поэтому некоторые эксперты рекомендуют начать именно с них.
Существует противоположное мнение, при котором начинать нужно с директивных зависимостей, поскольку так у разработчика будет больше возможностей повлиять на процесс устранения.
План-перехват
Какие же возможности есть у разработчика? Первым делом нужно подтвердить актуальность уязвимости для продукта — говорим, что да, такую атаку кто-то сможет провести и у нас все сломается. Это для директивных зависимостей.
Для тразитивных все несколько сложнее — мы должны построить трассу — цепочку вызовов в нашем приложении. Это очень похоже на киллчейн атаки и маршрут нарушителя из процесса расследования инцидентов. Бывает так, что трассы подтверждают только часть непрямых зависимостей, и объем работы по устранению существенно снижается. Однако, когда мы говорим о транзитивных зависимостях, починить их в своем коде бывает сложно; например, вы завели issue, найденную уязвимость пропатчили, но ваша директивно-зависимая библиотека не обновилась и работает со старой уязвимой версией — запатчить всю цепочку не представляется возможным. В таком случае эксперты предлагают пойти иным путем и работать с вызовами уязвимых методов в самом коде, проводить валидацию вызова и настраивать уже там безопасную обертку для защиты данных.
Существуют несколько популярных стратегий работы с найденными уязвимостями:
· Обновление — проверка следующей версии приложения или библиотеки с последующим обновлением. Тут следует отметить, что политики обновлений могут быть совершенно разные — кто-то предпочитает сидеть «на несколько версий» позади последней, потому что зачастую самая свежая версия кажется более безопасной только потому, что там еще не успели обнаружить что-то критичное. У кого-то мотивацией является непрерывность работы приложения — новая версия может означать как добавление, так и удаление функционала, который использовался в продукте. Стратегия «просто обновить всё» к сожалению, не панацея.
· Миграция. Бывает так, к примеру, что вы затянули в свой продукт библиотеку каким-то компонентом с множеством зависимостей только ради одной функции. Тогда следует взвесить с одной стороны риски от того, что приложение уязвимо, и от того, что придется потратить время и ресурсы на собственную разработку или на замену одной библиотеки на другую.
· Митигация. Защитить код можно иначе. Многие компании, или не дождавшись патча от автора библиотеки, или не имея возможности перехода на новую версию, занимаются патчингом сами и мигрируют это легаси из продукта в продукт. Есть и более удобные способы — например, обернуть продукт в СЗИ, которая разберется со всеми нежелательными вызовами и защитит чувствительные данные.
· Фриз всего. Здесь речь идет и об обновлении, и о разработке. Чаще всего такая стратегия применяется, когда речь идет о системообразующем компоненте системы. Бывали случаи, когда продукты не выпускали на рынок и переносили сроки внедрения систем заказчикам до того, как уязвимость не будет устранена тем или иным способом.
Самое важное
Основное, что хотелось бы подчеркнуть по итогу для запоминания, это то, что SCA — процесс регулярный. Он должен развиваться вместе с продуктом, повышая его уровень безопасности. Как только очередная ступень пройдена, можно переходить к следующей, не бояться обновлений и на первый взгляд пугающе большого графа зависимостей — всё поправимо.
Важно быть в курсе того, что используется приложением, из чего оно состоит. Только благодаря грамотно внедренному и выстроенному процессу безопасной разработки, чьей основой и является компонентный анализ, можно сделать что-то по-настоящему защищенное.