Разглядываем CodeScoring с помощью Natch
Последние несколько лет вопросы разработки безопасного и качественного ПО (далее – РБПО) стабильно входят в число наиболее приоритетных для практически любого отечественного ИТ-бизнеса. В России вопросами продвижения культуры РБПО, равно как и требований к безопасности и качеству ПО, занимается ФСТЭК России, являющаяся регулятором в области защиты информации. Под эгидой Центра компетенций ФСТЭК России и Института системного программирования РАН им. В. П. Иванникова, являющегося технологическим центром мирового уровня в области программной инженерии и РБПО, а заодно и органом по сертификации процессов РБПО (см. ГОСТ Р 56939-2024 Защита информации. Разработка безопасного программного обеспечения. Общие требования), с 2019 года формируется и развивается сообщество РБПО, включающее в себя многих отечественных разработчиков, экспертов, чиновников и бизнесменов, часть которых приняли участие в создании этой статьи.
ГОСТ Р 56939-2024 описывает общие требования к процессам РБПО – от образования, до технической поддержки, от статического, до динамического анализа, однако особое внимание уделяется определению поверхности атаки, то есть поиску множества потенциально уязвимых функций и модулей ПО, занимающихся обработкой пользовательских данных или чувствительной информации, а также интерфейсов, через которые эти данные поступают.
Обычно поверхность атаки определяют экспертным методом, однако тут же возникает вопрос полноты этого метода: что если эксперт пропустил действительно важные функции, участвующие в обработке данных, поскольку ПО имеет распределенную архитектуру, за потоком данных которой очень сложно следить извне, или выбрал такие функции для фаззинга, которые даже не задействованы при обработке пользовательских данных? Какие тогда функции выбирать?
На этот большой вопрос пытается ответить инструмент для определения поверхности атаки Natch, разрабатываемый небольшим коллективом в лаборатории ИСП РАН из Великого Новгорода. Natch — это инструмент, использующий динамический анализ, для определения поверхности атаки приложений и систем, основанный на полносистемном эмуляторе QEMU. Для этого исследуемое приложение помещается в виртуальную машину (далее — гостевая система), которая запускается под контролем Natch (далее — хостовая система).
Небольшое отступление: в лаборатории есть Музей вычислительной техники, который организовал Павел Довгалюк, ведущий инженер ИСП РАН и главный разработчик инструмента Natch!
Также для исследования возможности применения инструмента Natch при анализе решений в контейнерном исполнении (в виде распределенной архитектуры) был проведен небольшой Хакатон составом компаний, заинтересованных в развитии практик РБПО:
ИСП РАН, на базе института сформирован Центр исследований безопасности системного программного обеспечения под эгидой ФСТЭК России, целью которого является повышение уровня безопасности отечественного ПО;
CodeScoring, разработчик платформы безопасной разработки CodeScoring;
Базальт СПО, разработчик ОС Альт Linux, развивающий репозиторий свободного ПО;
НТЦ Фобос‑НТ, испытательная лаборатория, основными направлениями деятельности которой являются сертификация средств защиты информации и внедрение и развитие процедур РБПО.
В качестве модельного примера для анализа стал продукт CodeScoring, в части функциональности композиционного анализа ПО, предназначенного для разбора зависимостей, поиска уязвимостей в open-source, выявления совместимости open-source лицензий.
Общий алгоритм работы с инструментом Natch
Алгоритм работы с инструментом Natch состоит из следующих этапов:
Подготовка ВМ
Отключение/удаление лишних сервисов, таких как GUI и, например, unattended-upgrades в Ubuntu, что позволит получить результаты без лишнего «шума» в виде процессов, незадействованных в сценарии.
Подготовка объекта оценки
Natch имеет смысл применять при whitebox тестировании (когда нам известно про объект все, вплоть до исходных текстов), поэтому ПО, написанное на компилируемых языках программирования стоит собирать с отладочной информацией и без оптимизации. Данный подход позволит получить в результатах анализа не просто смещение, а конкретные названия функций, что значительно упростит задачу по поиску функций, лежащих на поверхности атаки.
Определение содержания сценария
Подготовка пользовательского сценария взаимодействия с ПО, например, процесс авторизации или загрузки файла.
Запись сценария с четко определенными временными границами
В общем случае нам не интересен процесс запуска ядра при старте системы, поэтому мы можем выделить определенный временной интервал работы системы, в котором реализован сценарий с помощью функции создания снапшотов системы.
Пометка выбранных входных данных
Natch может создавать пометки только на основе файлов, сети и трафика от USB устройств.
Воспроизведение сценария
Тут ничего нажимать не нужно, Natch сам делает всю работу.
Анализ поверхности атаки в графической подсистеме SNatch
Подробнее с процессом работы с инструментом можно ознакомиться в документации, а также попробовать провести анализ результатов в SNatch на тестовых примерах.
Тестовый стенд
CodeScoring является распределенным монолитом (рисунок 3). Для анализа было выбрано три модуля, которые технологически отличались друг от друга:
Консольный агент Johnny – исполняемый бинарный файл, написанный на компилируемом языке программирования Golang.
Модуль Backend – сервис, поднятый в контейнере, написанный на интерпретируемом языке программирования Python, который в процессе сборки компилируется в Extension Module.
Модуль Frontend – веб-интерфейс, написанный на интерпретируемом языке программирования TypeScript, который преобразуется в JavaScript.
Хостовая система, из которой производился запуск Natch, и Гостевая система (ВМ QEMU), в которой запускалось приложение, были на Ubuntu Server 22.04.3 LTS. Такой выбор обусловлен уменьшением «шумов» от неинтересных для анализа процессов и удобством взаимодействия через терминал.
Для упрощения работы с виртуальными машинами из одной терминальной сессии было проброшено ssh соединение до ВМ QEMU (рисунок 4).
Тестируем консольный агент Johnny
Консольный агент Johnny – исполняемый бинарный файл, который осуществляет разбор манифестов известных пакетных менеджеров, сканирование Docker-образов и поиск прямых включений open-source библиотек по хэшам.
При работе в режиме сканирования директорий с исходным кодом, агент рекурсивно обходит директорию, указанную в параметрах запуска, и осуществляет поиск и разбор манифестов.
По окончанию работы формируется SBOM файл, и в консоль выводится информация о найденных уязвимостях и сработавших политиках.
Подготовка
Консольный агент написан на компилируемом языке программирования Golang, следовательно, перед реализацией сценария был подготовлен бинарный файл, собранный с отладочной информацией:
go build -gcflags=all="-N -l"
После сборки полученный бинарный файл был добавлен в перечень модулей с символьной информацией. Это можно сделать либо при создании проекта в Natch, либо с помощью добавления модулей через команду natch modules, если проект уже был создан.
Сценарий
Консольный агент осуществил разбор golang-манифестов, сформировал SBOM файл и вывел в консоль отчет об уязвимостях.
В параметрах запуска передавались файлы go.mod и go.sum.
./johnny scan dir path/to/dir --api_token <token> --api_url https://<host>
где <token> – api-токен пользователя, проводящего анализ, а https://<host> – url-адрес инсталляции.
Источники пометок
В качестве источника пометок (с их помощью можно отслеживать продвижение данных в системе) данных применялись файлы go.mod и go.sum. Taint конфиг выглядит следующим образом:
[TaintFile]
list=go.mod;go.sum
Результаты
Загрузив результат анализа в SNatch мы видим Граф процессов (рисунок 5), в котором явно отображены источники пометки (файлы go.mod и go.sum), бинарный файл johnny (синий прямоугольник с закругленными углами) и входящий через сокет tcp трафик (оранжевый овал).
Перейдем во вкладку Поверхность атаки (рисунок 6), где представлен древовидный ранжированный список функций: функции, выделенные синим цветом, непосредственно участвовали в обработке помеченных данных, а неокрашенные участвовали транзитивно.
Стоит отметить, что инструмент показал в топ-3 функции исходного текста parsers.go, содержащего различные парсеры, которые можно использовать для проведения фаззинг-тестирования. Результат работы инструмента совпал с ожидаемыми функциями. Иными словами, мы получили инструментальное подтверждение поверхности атаки.
Функции рантайма и memmove(), а также стандартные функции работы со строками в контексте исследования нам неинтересны.
Также были «подсвечены» функции XORKeyStream() пакета шифрования chacha20 из состава зависимостей консольного агента, которые участвовали в реализации TLS протокола (https), при этом не были «подсвечены» сетевые пакеты (рисунок 7), поскольку трафик был зашифрован, хотя консольный агент точно отправлял помеченные данные в сеть.
Во вкладке Ресурсы помимо сетевых пакетов также представлены файлы, с которыми работал консольный агент johnny с указание чтения или записи: он произвел запись в файл bom.json – сформированный SBoM файл.
Использование ресурсов
Время работы сценария после создания контрольной точки с помощью savevm (natch record): ~20 секунд
Время работы воспроизведения сценария с отслеживанием помеченных данных (natch replay): 20 минут, 52 секунды
Количество проанализированных инструкций: ~2.7 млрд
Тестируем модуль Backend
Подготовка
Для анализа был отредактирован стандартный установочный docker-compose файл с целью упрощения запуска сервисов, в частности, были отключены запуски очередей. СУБД Redis и PostgreSQL с функцией миграции данных были подняты до выполнения сценария.
docker compose up -d redis
docker compose up -d postgres
docker compose run backend-migrate
Модуль Backend написан на интерпретируемом языке программирования Python и в процессе сборки компилируется в Extension Module (.so-библиотеки) с помощью компилятора Nuitka, методы которого вызываются с помощью скрипта manage.py. Extension Module собирался с отладочной информацией.
После сборки бинарные файлы, .so-библиотеки внутри контейнера были добавлены в перечень модулей с символьной информацией. Это можно сделать при создании проекта или с помощью добавления модулей через команду natch modules.
Интерпретатор Python «вручную» собирать не потребовалось, поскольку при создании проекта или обновления модулей с символьной информацией Natch автоматически находит недостающую отладочную информацию на доступных официальных хранилищах.
Сценарий
Создание через интерфейс командной строки суперпользователя с правами администратора для последующего входа в систему.
Поскольку создание суперпользователя происходит в интерактивной терминальной сессии, а Natch может создавать пометки только на основе файлов, сети и трафика от USB устройств, то появилась необходимость в написании bash-скрипта, который читал из файлов значения задаваемых переменных (логин и пароль) и подставлял их в интерактивную сессию.
Данный bash-скрипт выглядит следующим образом:
echo "NatchUser" > ./login.txt
echo "NatchPassword" > ./password.txt
docker compose run \
-e DJANGO_SUPERUSER_USERNAME=$(cat ./login.txt) \
-e DJANGO_SUPERUSER_PASSWORD=$(cat ./password.txt) \
-e DJANGO_SUPERUSER_EMAIL=natch@demo.example \
backend \
python3 ./manage.py \
createsuperuser --no-input
Источники пометок
В качестве источника пометок данных применялись файлы login.txt и password.txt, которые передавались в bash-скрипте в интерактивную сессию ввода логина и пароля.
Результаты
На Графе процессов (рисунок 11), если начать разбирать этот клубок, видно, как помеченные файлы прошли следующий путь:
данные файлов были считаны с помощью cat через bash-скрипт;
через однонаправленный канал межпроцессного взаимодействия pipe() они попали в процесс docker, который инициировал процесс плагина docker compose;
далее docker compose через docker.sock произвел взаимодействие с dockerd daemon, который с помощью containerd (утилита, реализующая исполняемую среду для запуска контейнеров) запустил runc (низкоуровневая среда выполнения контейнеров, которая создает и запускает контейнеры);
runc же как раз и выполнил python3 процессы внутри изолированного окружения (контейнер backend);
python3 manage.py делает запись помеченных данных по внутренней сети docker в таблицу СУБД postgres, расположенной в контейнере cont_1.
Механизм распознавания связей процессов для построения Графа процессов иногда работает недостаточно точно, что проявляется, например, в неверной связи между процессами git и runc[1:CHILD]. Это может ошибочно указывать на запуск контейнера через git, хотя на самом деле, судя по Временному графу (рисунок 12), git выполнял свою глобальную инициализацию. Во вкладке Ресурсы (рисунок 13) в процессе git подсвечивается только ядро (помеченные данные находятся в переменных окружения DJANGO_SUPERUSER_USERNAME и DJANGO_SUPERUSER_PASSWORD), что опять же дает понять, что Граф процессов не точно разобрал данную взаимосвязь.
Также по графу явно выделяются процессы, запущенные от root, они окрашены в светло-розовый цвет. Процесс dockerd окрашен в ярко-розовый цвет, поскольку он также занимался обработкой помеченных данных.
Во вкладке Поверхность атаки (рисунок 14) отображены исполняемые файлы, которые действительно фигурировали при выполнении сценария, что сходится с нашими ожиданиями.
В CodeScoring используется web фреймворк Django и во Вкладке ресурсы (рисунок 15) видно, Django хранит пароли согласно данному описанию (https://docs.djangoproject.com/en/5.1/topics/auth/passwords/#how-django-stores-passwords).
Во вкладке Граф вызовов (рисунки 16 и 17) при фильтрации по ключевому слову nuitka отображены функции и соответствующие им Extension модули с указанием исходных текстов на C.
Таким образом, получено подтверждение взаимодействия .so-библиотек с помеченными данными, которые дошли до __memcpy_sse2_unaligned(). Полученные результаты позволяют приоритизировать статический анализ сформированного после компиляции Nuitka кода.
Использование ресурсов
Время работы сценария в QEMU 6.2.0 без Natch с флагами -enable-kvm -cpu host,nx: 8 секунд
Время работы сценария после создания контрольно точки с помощью savevm (natch record): 2 минуты 14 секунд
Время работы воспроизведения сценария с отслеживанием помеченных данных (natch replay): ~16 часов
Время построение флеймграфа в SNatch: ~9 часов
Количество проанализированных инструкций: ~19.8 млрд
Рекомендации
При подготовке к анализу ПО с микросервисной архитектурой (например, кластер контейнеров) необходимо провести аналитическую работу по выбору конкретных узлов, которые лежат на общей поверхности атаки, и начинать анализ с них. Нет смысла в запуске Natch на большой распределенной системе, поскольку сам по себе анализ может занять времени больше, чем потребуется времени для Тепловой смерти Вселенной.
Лучше всего подготавливать короткие сценарии (ввод логина/пароля, активация лицензии, отправка http-запроса, загрузка файла и т.д.), поскольку чем сложнее сценарий, тем больше времени уйдет на его воспроизведение с отслеживанием помеченных данных.
Для сокращения времени анализа также желательно взаимодействовать с гостевой системой через консольный интерфейс, а не через GUI, поскольку GUI тащит за собой ненужные библиотеки, которые тратят процессорное время и в целом затрудняют автоматизацию: лучше создать bash-скрипт, который выполнит детерминированные действия без лишнего «шума» в логах.
По времени анализа можно ориентироваться на следующее:
Действие | Замедление, разы |
Реализация сценария в QEMU-KVM без Natch | 1 |
Реализация сценария при его записи в Natch | ~10-20 |
Реализация сценария при его воспроизведении в Natch | ~50-500* |
*в зависимости от сложности сценария
Если реализация сценария в QEMU-KVM (то есть, с аппаратной виртуализацией) занимает ~10 с, то при его записи произойдет замедление до ~100-200 с (тут аппаратную виртуализацию уже использовать нельзя), а воспроизведение займет от ~5000 до ~50000 c для более нагруженных сценариев. Таким образом на анализ одного среднего сценария может потребоваться до ~15 часов без учета времени на создание проекта и на загрузку отладочной информации модулей.
Для анализа сценария предпочтительным является процессор, обеспечивающий наибольшую производительность на одно ядро. Это связано с тем, что сценарий записывается в режиме полносистемной эмуляции вычислительных ресурсов гостевой системы, при этом задействуется одно вычислительное ядро хостовой системы. Многоядерность системы при анализе не играет никакой роли. Лучше выбрать одно быстрое ядро, чем много медленных.
Команда Natch рекомендует применять следующее оптимальное соотношение ОЗУ 1 к 2 гостевой и хостовой системы при подготовке к работе с инструментом и отключать swap. Например, на текущем стенде было выделено 40 Гб ОЗУ на гостевую систему и 96 Гб на хостовую с Natch.
Также стоит уделить внимание и объему памяти на жестком диске: результатом анализа Natch является сформированный .tar.zst-архив. Сам по себе архив может быть размером от 500 Мб до 20 Гб в зависимости от продолжительности сценария. После загрузки архива в SNatch его размер может увеличиться кратно: для архива размером 20 Гб итоговый размер составит 160 Гб.
Вывод
По итогу Хакатона были сформированы перечень feature request в отношении инструмента и методические рекомендации по подготовке решений с распределенной архитектурой для анализа более тяжелыми инструментами динамического анализа.
Была подтверждена корректность анализа работы инструмента, то есть Natch можно (и нужно) использовать для верификации поверхности атаки или же для ее анализа вслепую.
К сожалению, модуль Frontend не удалось успешно проанализировать, поскольку на момент проведения Хакатона поддержка JavaScript была только в dev-версии Natch, однако из этого менее удачного опыта родились предложения по улучшению инструмента.
UPDATE: Пока статья готовилась, 3 марта 2025 года вышел релиз Natch 3.3, в котором появилась полноценная поддержка JavaScript (движок V8), в том числе произошло ускорение процесса replay примерно в 1,5 раза! С полным перечнем изменений можно ознакомиться здесь: https://t.me/ispras_natch/3137.
Авторы статьи: Слепых Андрей, Бесперстов Никита, Довгалюк Павел