Категория A9 из OWASP Top Ten 2017 (ставшая A6 в OWASP Top Ten 2021) посвящена использованию компонентов с известными уязвимостями. Для её покрытия в PVS-Studio разработчикам придётся превратить анализатор в полноценное SCA-решение. Как же анализатор кода будет искать уязвимости в используемых компонентах? И что ещё за SCA? Ответы на эти и многие другие вопросы ждут вас в этой заметке.
Одним из приоритетных направлений развития PVS-Studio является покрытие категорий из списка OWASP Top Ten 2017 в C# анализаторе (а в будущем займёмся и 2021). Из них самой необычной для нас является A9:2017 - Using Components with Known Vulnerabilities, которая в предварительной версии OWASP 2021 оказалась на позиции A6. Реализация правила для этой категории является для нашего анализатора важной задачей, выполнение которой позволит классифицировать PVS-Studio как средство SCA (Software Composition Analysis). Какой же подход к реализации нам лучше выбрать? Давайте разбираться!
Категория угроз A9 (в предварительной версии OWASP 2021 она превратилась в A6) посвящена использованию компонентов с известными уязвимостями, то есть таких компонентов, для которых есть соответствующая запись в базе CVE. CVE (Common Vulnerabilities and Exposures) – это база записей о реальных уязвимостях в программном обеспечении, оборудовании, сервисных компонентах и т. п.
A9 является достаточно нетипичной с точки зрения её покрытия в PVS-Studio, так как существующая архитектура анализатора рассчитана на поиск ошибок в самом коде. В ней используются синтаксические деревья, семантическая модель, различные технологии, вроде анализа потока данных и т.д. Этих возможностей было в целом достаточно для реализации диагностических правил, которые будут покрывать те или иные категории из OWASP Top Ten 2017.
К примеру, на основе уже существовавшего механизма data-flow был реализован taint-анализ и различные диагностические правила на его основе:
Каждое из этих правил так или иначе ищет потенциальные уязвимости в коде и работает при обходе синтаксического дерева. При этом все они соотносятся с одной или несколькими категориями из OWASP Top Ten 2017 (полный список соответствий можно найти по ссылке).
Совершенно иная ситуация с A9. С точки зрения анализа C#-проектов реализация правила для неё представляет собой проверку всех библиотек-зависимостей проекта на наличие CVE. Иначе говоря, для каждой зависимости нужно проверять, нет ли в базе CVE соответствующей записи.
Такая задача сильно выходит за рамки привычного обхода синтаксического дерева и исследования семантики кода, однако мы твёрдо намерены покрыть эту категорию. Кроме того, очень важно, что реализация такого правила позволит позиционировать PVS-Studio как SCA-решение.
В общем и целом, SCA-инструменты предназначены для проверки проекта на наличие проблемных зависимостей.
Например, если проект зависит от библиотеки с открытым исходным кодом, то крайне важно учитывать лицензию, по которой эта библиотека распространяется. Нарушение условий использования может повлечь за собой огромный ущерб бизнесу.
Другой возможной проблемой является наличие уязвимостей в используемой библиотеке. В контексте SCA речь идёт, конечно же, об известных уязвимостях – CVE (определить использование зависимости, содержащей нигде не записанную уязвимость – задача практически нереальная :) ). Нетрудно догадаться, что использование библиотеки с уязвимостью (да ещё и публично известной) вполне может сделать и сам продукт уязвимым к различного рода атакам.
Кроме того, весьма опасным подходом считается использование библиотек, поддержка которых была прекращена. Потенциально такие зависимости также содержат уязвимости, но при этом о них, скорее всего, не будет известно. Об исправлении такой проблемы и вовсе не может быть и речи, ведь заниматься этим исправлением уже никто не будет.
И вот мы постепенно подходим к главному вопросу – а как же реализовать функционал SCA? В первую очередь стоит отметить, что в PVS-Studio эти возможности будут разрабатываться в рамках покрытия категории A9:2017 (Using Components with Known Vulnerabilities). Из этого следует, что мы, в первую очередь, будем искать именно зависимости с известными уязвимостями. Впрочем, уже сейчас в анализаторах PVS-Studio присутствуют диагностические правила, предупреждающие о наличии copyleft лицензий:
Не исключено, что со временем мы будем реализовывать и другие возможности SCA.
Обнаружение компонентов с известными уязвимостями сводится к получению всех (как прямых, так и транзитивных) зависимостей проекта и последующему поиску для каждой из них соответствующей CVE. И если первая часть этого плана кажется достаточно тривиальной, то со второй уже возникают проблемы.
На данный момент указанный функционал планируется реализовать для C# анализатора. Получение же списка зависимостей для проекта на C# производится довольно просто – в этом нам сильно помогает Roslyn, на основе которого и построен анализатор. Если говорить точнее, то главным фактором является использование для всех C#-проектов по сути одной и той же платформы для сборки (MSBuild) и одного компилятора. В то же время Roslyn тесно связан с MSBuild, что и делает шаг получения списка зависимостей весьма тривиальным.
Несколько сложнее это будет реализовать для анализаторов C++ и Java, поскольку экосистема у этих языков намного более многообразна... Этим мы займёмся в другой раз :).
Итак, зависимости проекта получены – как же понять, в каких из них есть уязвимости? При этом нужно учесть, что уязвимость может быть актуальна лишь для конкретных версий библиотеки. Очевидно, что нужна какая-то база, в которой будут храниться соответствия зависимостей, версий и соответствующих им CVE.
Собственно, главный вопрос реализации и состоит в поиске (или, быть может, создании) такой базы, которая позволит сопоставлять имеющуюся информацию о зависимостях проекта с конкретными CVE. Ответ на данный вопрос у различных инструментов разный.
Первый рассмотренный нами вариант – подход, используемый в OWASP Dependency Check. Его суть проста - для каждой зависимости данная утилита производит поиск соответствующего идентификатора в базе CPE (Common Platform Enumeration). По сути, база CPE является некоторым списком записей с информацией о продуктах, их версиях, вендорах и прочем. Для реализации SCA здесь особенно важна возможность получения соответствий CPE и CVE. Таким образом, получение списка CVE для зависимости сводится к поиску соответствующей записи в базе CPE.
Саму базу CPE и соответствия с CVE можно найти на официальном сайте National Vulnerability Database. Одним из вариантов получения нужной информации является использование Rest API, описанного здесь. К примеру, следующий запрос позволяет получить первые 20 элементов базы CPE с включением в них соответствующих CVE:
https://services.nvd.nist.gov/rest/json/cpes/1.0?addOns=cves
Ниже представлен пример CPE для ActivePerl:
{
"deprecated": false,
"cpe23Uri": "cpe:2.3:a:activestate:activeperl:-:*:*:*:*:*:*:*",
"lastModifiedDate": "2007-09-14T17:36Z",
"titles": [
{
"title": "ActiveState ActivePerl",
"lang": "en_US"
}
],
"refs": [],
"deprecatedBy": [],
"vulnerabilities": [ "CVE-2001-0815", "CVE-2004-0377" ]
}
Наибольшую ценность тут представляет значение "cpe23Uri", содержащее важную для нас информацию в определённом формате, и, конечно же, "vulnerabilities" (хотя они, по сути, не являются частью списка CPE). Для простоты будем пока что читать строку "cpe23Uri" как
cpe:2.3:a:<vendor>:<product>:<version>:<update>:...
Согласно спецификации, дефис на месте одного из фрагментов означает логическое значение "NA". Насколько я понимаю, это можно интерпретировать как "значение не задано". Записанная же на месте фрагмента "*" означает "ANY".
При реализации решения, основанного на CPE, главная сложность состоит в поиске нужного элемента для каждой зависимости. Дело в том, что название библиотеки, полученное при разборе ссылок проекта, может не соответствовать названию в соответствующей записи CPE. Например, в списке CPE присутствуют записи со следующими "cpe23Uri":
cpe:2.3:a:microsoft:asp.net_model_view_controller:2.0:*:*:*:*:*:*:*
cpe:2.3:a:microsoft:asp.net_model_view_controller:3.0:*:*:*:*:*:*:*
cpe:2.3:a:microsoft:asp.net_model_view_controller:4.0:*:*:*:*:*:*:*
cpe:2.3:a:microsoft:asp.net_model_view_controller:5.0:*:*:*:*:*:*:*
cpe:2.3:a:microsoft:asp.net_model_view_controller:5.1:*:*:*:*:*:*:*
Обработав записи, анализатор сделает вывод, что все они связаны с различными версиями некоего продукта "asp.net_model_view_controller", выпущенного некой компанией Microsoft. Всем этим записям соответствует уязвимость с идентификатором CVE-2014-4075. Фактически же библиотека, в которой была обнаружена уязвимость, называется "System.Web.Mvc", и скорее всего именно это название будет получено из списка зависимостей. В CPE же записано как бы название продукта – "Microsoft ASP.NET Model View Controller".
Кроме того, важно учитывать автора библиотеки (vendor), идентификатор которого является неотъемлемой частью записей CPE. С этим также есть проблемы – далеко не всегда фактическая зависимость предоставляет нужную информацию в какой-либо подходящей для разбора форме, не говоря уж о соответствии какой-либо записи из CPE.
Нетрудно догадаться, что похожие сложности возникают и с версией.
Ещё одна беда заключается в том, что многие записи в базе совершенно не интересны при поиске соответствий. Возьмём для примера запись, приведённую в начале данного раздела:
cpe:2.3:a:activestate:activeperl
ActivePerl — дистрибутив языка Perl от ActiveState. Вероятность того, что что-то подобное будет являться зависимостью проекта на C#... Ну, невелика, как вы понимаете. Таких "лишних" (в контексте анализа C# проектов) записей - огромное количество, и сложно сказать, как именно можно научить анализатор отличать их от тех, что нам могли бы быть полезны.
Несмотря на вышеперечисленное, подход с использованием CPE всё же может быть эффективен – просто его реализация должна быть куда хитрее банального сравнения пары строк. К примеру, весьма интересным образом работает всё тот же OWASP Dependency Check, собирающий для каждой зависимости строки "признаков" (evidence), которые могут соответствовать значениям vendor, product и version из искомого CPE.
Другой обнаруженный нами подход к поиску CVE состоит в исследовании GitHub Advisory на предмет наличия записи, соответствующей проверяемой зависимости. GitHub Advisory – это база уязвимостей (CVE), обнаруженных в open source проектах, хранящихся на GitHub. Полный список позиций доступен по ссылке.
После знакомства с CPE стало очевидно, что при выборе исходного источника данных, крайне важным вопросом является способ их записи. И стоит признать, что GitHub Advisory в этом плане куда удобнее, чем CPE. Возможно, что данная база изначально создавалась как раз с целью использования различными SCA-инструментами. Во всяком случае, она действительно используется решениями вроде GitHub SCA или SCA от Microsoft.
Для программного доступа к элементам GitHub Advisory необходимо использовать GraphQL. Это достаточно мощная технология, однако нельзя не отметить, что разобраться с простым Rest API гораздо проще. Тем не менее, знатно намучавшись с GitHub's GraphQL Explorer, я всё-таки смог сконструировать запрос, который выдал примерно то, что я хотел, а именно – список пакетов и связанных с ними CVE. Вот один из полученных мною элементов:
{
"identifiers": [
{
"value": "GHSA-mv2r-q4g5-j8q5",
"type": "GHSA"
},
{
"value": "CVE-2018-8269",
"type": "CVE"
}
],
"vulnerabilities": {
"nodes": [
{
"package": {
"name": "Microsoft.Data.OData"
},
"severity": "HIGH",
"vulnerableVersionRange": "< 5.8.4"
}
]
}
}
Очевидно, я составил не самый оптимальный запрос, поэтому на выходе я получил немного лишней информации.
Если вы знаток GraphQL, то пожалуйста, напишите в комментарии, как бы вы сконструировали запрос, который позволяет получить список соответствий вида (имя пакета, версия) => список CVE.
Так или иначе, в результатах запроса чётко указано имя пакета (то самое, которое соответствует этой зависимости в NuGet), соответствующие CVE и версии, для которых уязвимости актуальны. Я уверен, что, разобравшись с этой темой получше, мы легко смогли бы написать утилиту, которая бы автоматически скачивала бы всю необходимую информацию.
Отмечу, что тут очень порадовала возможность отобрать пакеты именно для NuGet. В большинстве случаев (если не во всех) именно среди них хотелось бы искать записи, соответствующие той или иной зависимости, минуя всякие штуки для Composer, pip и т. д.
Увы, но и это решение не без ложки дёгтя. На момент написания этой заметки в базе GitHub Advisory находилось всего 4753 записи – а NuGet-пакетов среди них всего 140. В сравнении с базой CPE, содержащей более 700 тысяч записей, данная коллекция выглядит не очень внушительно (хотя тут стоит отметить, что не для каждого CPE есть соответствующие CVE). Кроме того, база GitHub Advisory, судя по описанию, будет содержать информацию об уязвимостях исключительно open-source проектов, лежащих на GitHub. Очевидно, это сильно сужает выборку.
Тем не менее, удобство представления уязвимостей в данной базе как минимум заставляет задуматься о том, чтобы использовать её если не как основной, то по крайней мере как один из вспомогательных источников данных.
Мощные SCA средства, такие как Black Duck и Open Source Lifecycle Management, формируют и используют свои собственные базы, которые, судя по описанию, содержат даже больше информации, чем можно найти в National Vulnerability Database. Очевидно, что информация в таких базах представлена в наиболее удобном для соответствующих инструментов виде.
Работая над данным направлением, мы в любом случае будем вынуждены преобразовывать найденные публичные данные об уязвимых компонентах в некоторый вид, удобный для нашего анализатора (остаётся лишь найти удобные для такого преобразования данные). Скорее всего все SCA-инструменты имеют собственные базы уязвимых компонентов, однако далеко не все из них содержат какую-нибудь информацию об уязвимостях, которых нет в NVD или каком-нибудь другом публичном источнике. Формирование по-настоящему "своей" базы, превосходящей аналогичные базы других средств, является крайне важным отличительным признаком мощных SCA-решений. Поэтому, работая над реализацией SCA в PVS-Studio, мы будем учитывать необходимость расширения нашей базы уязвимостей в будущем.
Может показаться, что реализация функционала SCA в PVS-Studio потребует создание чего-то принципиально нового, без возможности использовать какие-либо наши существующие наработки. И, честно говоря, не зря. Дело в том, что анализ зависимостей – действительно совершенно новый функционал и ничего подобного в PVS-Studio сейчас нет.
Тем не менее, у нас есть идея, как можно использовать существующую архитектуру, чтобы сделать нашу реализацию SCA лучше. Вместо того, чтобы попросту ругаться на наличие ссылки на небезопасную библиотеку, мы постараемся искать именно её использование в коде. Уж для этого-то у нас полно готовых механизмов :).
На мой взгляд, если библиотека даже и не используется, то предупредить о её наличии среди зависимостей всё равно стоит. А уж если её возможности как-либо применяются в коде, то предупреждение стоит выдать с наивысшим уровнем. Однако пока это лишь размышления.
Как вы понимаете, конкретный способ реализации ещё не определён нами окончательно, и некоторые вопросы пока не разрешены. К примеру, если библиотека с уязвимостью используется много раз в проекте – стоит ли выдавать предупреждение на каждое место использования? Или пользователь утонет в срабатываниях? Возможно, стоит выдавать одно предупреждение на файл, а может и вовсе просто повышать уровень, если найдено использование?
Подобных вопросов в этом начинании ещё очень много, поэтому нам было бы интересно узнать – каким бы ВЫ хотели видеть SCA в PVS-Studio? Как должен работать эффективный инструмент для поиска проблемных уязвимостей? Какой уровень должен быть у предупреждений? Стоит ли попробовать найти другие источники информации об уязвимостях? Стоит ли ругаться на транзитивные (непрямые) зависимости?
В общем, ждём ваших комментариев и большое спасибо за внимание!