
Всем привет! ? ? ? Мы стажеры — разработчики Тинькофф: Влад, Паша и Илья. В проекте по стажировкам в ИБ Summer of Code под руководством Ромы Лебедя мы реализовали анализатор бинарного кода на основе ML-подходов — Binary SCA. Наш проект совмещает две предметные области — информационную безопасность и ML, поэтому мы разделили статью на несколько частей.
В статье поговорим о подходах к компонентному анализу и почему нам не подошел ни один из них. Расскажем, зачем мы разработали свое решение и что означает аббревиатура SCAML.
Что такое компонентный анализ
Software Composition Analysis — процесс выделения и определения компонентов, из которых состоит программное обеспечение. Еще его называют компонентным анализом. Как правило, SCA используют для детекции связанных компонентов, библиотек и прочих транзитивных зависимостей.
Многие SCA-инструменты находят лицензии, старые зависимости без поддержки, уязвимости и потенциальные эксплойты. Отдельные инструменты умеют генерировать в специальном формате список компонент, из которых состоит проект. Такой список называется Software Bill Of Materials и подробно описан в словаре кодмайнера на codescoring.ru. SBOM многократно повышает ценность компонентов. Он часто требуется для различных процедур сертификации. А сообщество OWASP имеет отдельный проект и стандарт, посвященный безопасности использования и разработки компонентов.
Проектов с открытым исходным кодом появляется все больше, и без инструментов SCA нельзя представить ни один большой продукт. Современный DevSecOps подразумевает проверку всех составляющих компонент на соответствие требованиям, чтобы обеспечить безопасность каждого этапа разработки.
Самое тяжелое в SCA — получить SBOM. Если мы знаем, из каких компонент состоит приложение, просканировать его на уязвимости не проблема.

Synopsys проанализировал более 1500 кодовых баз компаний и выяснил, что 90% кода — это Open Source и минимум 70% из них содержат известные уязвимости.
Важно помнить о транзитивных зависимостях. Open Source использует и другие зависимости, которые могут быть уязвимыми или содержать требование раскрыть исходный код, чего многие хотят избежать по разным причинам. Иногда одна зависимость может тянуть целый паровоз из нескольких уровней транзитивных зависимостей.

Самый показательный и знаменитый случай произошел с компанией Equifax в 2017 году. Из-за уязвимости в Apache Struts 2 злоумышленники завладели информацией о 143 млн американцев, включая полные имена, адреса, документы. Для 200 тысяч пользователей злоумышленники получили информацию о банковских картах. Инцидент произошел в мае 2017, хотя исправление уязвимости вышло еще в марте того же года.
Распространенный пример использования SCA — анализ образов или других артефактов: JAR-файлы, образ виртуальной машины.
Анализ метаинформации
Существуют разные подходы к выявлению компонент в приложениях. Основные варианты для построения SBOM и компонентного анализа: анализ метаинформации, строк или названий файлов.
Самый приятный случай для компонентного анализа — наличие метаинформации. Например, в Java для Maven-репозитория большинство информации сохраняется в pom.xml-файлах, которые остается правильно распарсить.
Основная работа анализатора заключается в правильном извлечении имен из файлов с информацией обо всех зависимостях и вывод в нужном формате.
Скан метаинформации реализован во многих системах, например в Trivy и Syft.

Trivy — open-source-инструмент для поиска уязвимостей в образах и различных проектах на Java, Python, GO и других. Его функциональность не ограничивается сканированием проектов, Trivy умеет исправлять ошибки конфигурации и анализировать Kubernetes. Для Java он ищет информацию в самих файлах — названия классов и библиотеки можно идентифицировать как уникальные pom- и gradle.lockfile-файлы. Для Python Trivy анализирует requirements.txt, .lock-файлы pipenv и poetry. Одним словом, инструмент анализирует файлы, которые оставили разработчики.

Syft — open source CLI-инструмент для генерации SBOM из файловых систем и контейнеров. Например, в Java он анализирует META-INF-файлы и пути к этим файлам. На основе полученных знаний Syft делает выводы о компонентах.
Плюсы анализа метаинформации:
высокая скорость работы;
понятный ход действий для разработчика, полностью открытая система;
дешево с точки зрения мощностей;
закрывает начальные потребности в качестве работы.
Минусы подхода:
метаинформации может не быть, например как в C++;
в данных может лежать неправильная или устаревшая информация;
подход автоматически не учитывает транзитивные зависимости.
Не всегда в окружении лежат только необходимые зависимости для программы. Бывает, что в одном окружении крутятся несколько сервисов и каждый использует какую-то свою часть. В идеале мы бы хотели понимать, что именно используется в той или иной программе. Опираться на метаинформацию не самая лучшая идея, потому что иногда она не поставляется вместе с приложением.
WhiteBox-анализатор пропустит файл, если информация о нем отсутствует. А файл может содержать уязвимости! Аналогичная ситуация и с путями в директориях. Такому подходу остается просто пожать плечами и сказать: «Я сделал все что мог».
Анализ строк или названий файлов
Анализ строк или названий файлов — BlackBox-анализатор, подход, при котором на всю логику компонентного анализатора мы смотрим с точки зрения «черного ящика». На вход поступают сырые файлы, а на выходе получаем SBOM.
Что происходит внутри данной системы? Все что угодно! Обычно внутри «черной коробки» проходит анализ строк или сигнатур, а еще анализ названий файлов и путей до них. Часто для выявления версий продуктов прибегают к регулярным выражениям на строках. Например, есть база различных пакетов. У пакетов есть названия. Если это был Microsoft Word, скорее всего, нужный нам файл так и будет называться — Microsoft Word. Аналогично с другими пакетами.
Плюсы подхода:
смотрим на информацию из файлов и точно не упустим зависимости;
все еще высокая скорость работы.
Минусы подхода:
если смотрим на названия файлов или пути файлов, то они могут измениться;
не устойчив к обфускации или сильной оптимизации.
Бывают кейсы, когда приложение, в том числе и без исходного кода, приходит разработчикам без какой-либо дополнительной информации о его внутренностях. Например, различные коммерческие библиотеки, распространяемые в виде собранных дистрибутивов. Мы не знаем ничего об их зависимостях и составе. Вендор тоже не может гарантировать полноту данных. В таких случаях мы приходим к концепции BlackBox-анализаторов. Такие анализаторы используют различные алгоритмы и эвристики, анализирующие код, для выделения компонент, из которых состоит приложение.

Самый простой подход в BlackBox-анализаторах — парсинг по константам или строкам в коде. На таких принципах работает абсолютное большинство компонентных анализаторов. Например, cve-bin-tool составляет некоторые уникальные строки и регулярные выражения, однозначно классифицирующие файл и вытаскивающие версию. Вот пример для детекции bash:
class BashChecker(Checker):
CONTAINS_PATTERNS = [
r"save_bash_input: buffer already exists for new fd %d",
r"cannot allocate new file descriptor for bash input from fd %d",
# Alternate optional contains patterns,
# see https://github.com/intel/cve-bin-tool/tree/main/cve_bin_tool/checkers#helper-script for more details
# r"bash manual page for the complete specification.",
# r"bash_execute_unix_command: cannot find keymap for command",
]
FILENAME_PATTERNS = [r"bash"]
VERSION_PATTERNS = [
r"Bash version ([0-9]+\.[0-9]+\.[0-9]+)"
] # this version string is extracted from "@(#)Bash version 5.1.4(1) release GNU"
VENDOR_PRODUCT = [("gnu", "bash")]
Black Duck сравнивает по строкам и путям директорий со своей базой данных.
У Syft тоже есть свой набор сигнатур, но их не так много — всего 33 штуки: bash busybox composer consul erlang gcc go haproxy helm httpd java julia libphp mariadb memcached mysql nginx node openssl percona-server percona-xtrabackup percona-xtradb-cluster perl php-cli php-fpm postgresql pypy python redis ruby rust traefik wp-cli
BlackBox на основе констант, строк и названий файлов работает в 80% случаев. Но бывают кейсы, когда явного версионирования продуктов нет, строковая информация отсутствует или разработчики приложений специально или случайно меняют код программы так, что его становится очень сложно идентифицировать. Это может быть связано с оптимизацией путем форка различных библиотек или попыткой запутать логику приложения, чтобы было сложнее сделать reverse engineering.
Анализ семантики кода
Усложнить процесс Reverse Engineering можно, например, с помощью обфускации. Обфускация удаляет всю строковую информацию или усложняет получение таких строк, а еще меняет код, чтобы это усложнило понимание, но не повлияло на функциональность.
В случае полного отсутствия строковой информации помочь в задаче компонентного анализа могут только алгоритмы сравнения кода на основе семантики программы: семантика — единственное, что осталось неизменным ?
Так появилась наша разработка — SCAML. Мы попробовали использовать ML для анализа семантики кода, отсюда и название: SCA + ML = SCAML. За основу взяли достижения в области NLP.
Плюсы подхода:
смотрим на семантику кода;
подходит при смене параметров сборки;
подход ориентирован на случаи, когда дистрибутив получен от неизвестного источника;
позволяет видеть различия в коде, несмотря на отсутствие исходников.
Минусы подхода:
медленно, нужно много мощностей;
требует настройки на каждый формат файлов и архитектуру.

С развитием нейронных сетей и NLP-моделей задача понимания семантики программы стала выглядеть не так уж и страшно. Разработаны модели для понимания контекста в обычных текстах, например Word2Vec или архитектура-трансформер. Эти наработки нашли свое применение не только в ChatGPT, но и в анализе кода. Например, модели CodeT5, CodeGen, CodeLlama — дообученные на коде трансформеры и уже применяются в различных задачах генерации и анализа кода.
Все эти подходы позволяют сравнивать участки кода на схожесть, то есть проводить анализ с точки зрения supervised learning, а в нашей задаче мы должны выделять компоненты, из которых состоит заданный проект. Это оказалась отнюдь не тривиальная задача, поскольку отдельные библиотеки могут занимать как 50% кода, так и не больше 1%,и это не единственная проблема ? Обо всем подробнее мы поговорим в следующей статье.
Выводы
Open-source-компоненты — ключевой элемент многих проектов. Их широкое использование подразумевает необходимость тщательно изучать состав приложений и взаимосвязи внутри его. В этой области остаются сложности, которые требуют новых подходов и алгоритмов, где машинное обучение проявляет свою силу. Мы уже движемся в этом направлении и приглашаем других исследователей присоединиться к развитию анализа open-source-компонентов вместе с нами.