Вступление
Привет, Хабр! С вами Владимир Исабеков, руководитель группы статического тестирования безопасности приложений в Swordfish Security. Современная разработка программного обеспечения требует не только функционального, но и безопасного API. Сегодня мы расскажем, как провести фаззинг-тестирование API c помощью инструмента RESTler, имея на руках только спецификацию API. Статья написана в соавторстве с нашим инженером по безопасности, Артемом Мурадяном @TOKYOBOY0701.
Что такое фаззинг
Фаззинг — это техника автоматизированного тестирования, при которой на вход программе подаются специально подготовленные данные. Они могут привести к ее аварийному или неопределенному поведению. Этот процесс обычно автоматизирован с помощью отладчика, подключенного к целевой программе. Он записывает информацию, если приложение дает сбой. Впоследствии это позволяет идентифицировать ошибочный код, который привел к поломке.
Важно отметить идейное отличие между фаззерами и сканерами. В первом случае тестирование нацелено на поиск ошибок и багов, а во втором на обнаружение уязвимостей. Также, стоит упомянуть, что RESTler кроме ошибок может дополнительно находить уязвимости, для этого он использует собственные правила.
На вкус и цвет все фаззеры разные, методы их классификации также отличаются. Рассмотрим примеры некоторых вариантов:
По методу генерации новых входных значений:
Mutation-based Fuzzers. Мутируют существующие входные данные, чтобы создать новые тестовые случаи;
Generation-based Fuzzers. Создают новые входные данные с нуля (или не совсем), основываясь на грамматиках.
По типу обратной связи:
Процент покрытия кода. Учитывают процент исходного кода программы, который был выполнен в процессе тестирования. В контексте фаззинга покрытие помогает понять, были ли задействованы новые участки ПО при подаче новых входных данных;
Код ответа. Для анализа API фаззеры используют ответ от сервера, поскольку при фаззинге веб-приложений не всегда можно эффективно получать информацию о покрытии кода;
Без обратной связи или брутфорс. Осуществляют систематический перебор значений.
По цели фаззинга:
Protocol-based Fuzzers. Фокусируются на тестировании безопасности сетевых протоколов и программ, обрабатывающих конкретные файловые форматы (изображения, архивы и документы и другое);
Library and Framework Fuzzers. Предназначаются для тестирования функций из готовых библиотек и пакетов;
Binary Fuzzers. Осуществляют проверку безопасности исполняемого бинарного кода;
API Fuzzers. Предназначаются для тестирования безопасности веб-сервисов и API. Эти классификации не являются абсолютными, и некоторые фаззеры могут включать в себя элементы нескольких видов. Подробнее с технологиями, которые используются при таком тестировании, можно ознакомиться в fuzzingbook.
Фаззинг REST API
Теперь поговорим конкретно о Stateful REST API-фаззинге с применением инструмента RESTler. Этот метод фокусируется на тестировании безопасности REST API. Stateful-фаззинг учитывает состояние сервера и предыдущие запросы, в отличие от Stateless-теста, который рассматривает каждый запрос к API как изолированный случай.
Воспользоваться методом Stateful REST API Fuzzing можно с помощью инструмента RESTler, разработанного компанией Microsoft. Он может фаззить веб-приложения с REST API, изучая спецификацию OpenAPI. RESTler генерирует тесты с помощью анализа обратной связи из ответов, полученных во время выполнений цепочек запросов. Инструмент выявляет зависимости между типами запросов, объявленных в OpenAPI.
Начало работы
RESTler может работать под Windows, Linux и macOS. Собрать его можно в виде докер-образа или классического бинарника. Рассмотрим основные этапы работы фаззера и его настройки на примере тестового сервера из самого RESTler-a (он находится в каталоге demo_server). Первый этап работы – генерирование грамматики на основании OpenAPI Specification (в формате JSON или YAML). Для этого скомпилируем спецификацию тестового сервера:
C:\restler_bin\restler\Restler.exe compile --api_spec C:\demo_server\swagger.json
Так мы создаем новый каталог Compile в директории, откуда производится запуск команды. Результаты компиляции сохраняются в нескольких файлах:
grammar.py и grammar.json. В грамматике содержится информация о параметрах запросов и различных зависимостях между ними. На первом скриншоте можно увидеть функции, которые будут сохранять результаты ответов сервера для их использования в последующих цепочках запросов (второе изображение);
dict.json— словарь, связывающий типы параметров запросов с наборами значений. Можно редактировать этот файл по своему усмотрению, например, добавить кастомные пейлоады через свойство restler_custom_payload и указать в нем константные значения. На изображении ниже мы видим поле restler_fuzzable_string, значит, этот параметр в запросе будет фаззиться. Аналогично и для других типов, начинающихся с restler_fuzzable. Можем наблюдать на скриншотах, что все эти параметры используются в грамматике;
Config.json – файл, с помощью которого можно кастомизировать настройки компилятора рестлера, например, подключить в нем заранее подготовленные грамматики или указать несколько спецификаций;
Engine_settings. Используется для указания настраиваемых параметров RESTler. Позволяет редактировать IP и порт тестируемой цели, кастомный генератор входных данных и последовательность запросов, а также многие другие форматы. Их можно указывать для каждого эндпоинта.
Тестирование спецификации
Второй этап работы с RESTler – это выполнение всех запросов в спецификации OpenAPI хотя бы один раз и получение в ответ кода состояния «200». Делается это для того, чтобы определить, какие запросы невозможно выполнить при текущей настройке фаззера. Запускаем тест командой:
C:\restler_bin\restler\restler.exe test --grammar_file C:\demo_server\Compile\grammar.py --dictionary_file C:\demo_server\Compile\dict.json --settings C:\demo_server\Compile\engine_settings.json —no_ssl
В консоли можно увидеть, сколько эндпоинтов было протестировано успешно:
В результате работы создается каталог Test\RestlerResults\experiment{...}\logs, в котором находятся файлы:
speccov.json – результаты тестирования для каждого запроса в грамматике;
main.txt – журнал, в котором документируются попытки выполнения каждого запроса;
network.testing.{threadID}.txt – файл, в котором регистрируется весь HTTP(S)-трафик, генерируемый RESTler, включая все выполненные запросы REST API и их ответы. Он может помочь в случае, если покрытие тестами неполное. Это позволяет проверить сгенерированные запросы и ответы, и, при необходимости, изменить спецификацию OpenAPI (если она неполная), добавить нужные значения в словарь или отредактировать грамматику вручную. Тестовый режим по умолчанию попытается успешно выполнить каждый запрос один раз. Например, если параметр запроса имеет несколько возможных значений и успешно выполняется при передаче первого, то остальные не будут проверяться. Если же необходимо проверить все значения параметров запросов, то это можно сделать с помощью аргумента test_all_combinations. Результаты для всех комбинаций параметров будут указаны в файле speccov.json.
Фаззинг (ezmode)
Когда уровень покрытия тестами спецификации нас устраивает, можно переходить непосредственно к фаззингу. Здесь есть два возможных варианта. По сути, такое тестирование – бесконечный процесс, поэтому можно воспользоваться режимом RESTler-а под названием Fuzz-lean. Он предполагает однократное выполнение эндпоинта и метода из грамматики для быстрого обнаружения возможных багов. Второй вариант – режим Fuzz, при котором проводится полное тестирование RestAPI.
Для запуска теста нашего примера в режиме Fuzz-lean выполним команду:
C:\restler_bin\restler\restler.exe fuzz-lean --grammar_file C:\demo_server\Compile\grammar.py --dictionary_file C:\demo_server\Compile\dict.json --settings C:\demo_server\Compile\engine_settings.json --no_ssl
В консоли можно увидеть, сколько протестировано эндпоинтов и какие найдены баги:
Результаты находятся в новом каталоге FuzzLean\RestlerResults\experiment{...}, схожем с тем, что создавался при фазе Test с результатами покрытия. Но сейчас он содержит еще и bug_buckets со списком уникальных последовательностей запросов, которые привели к багам. Если мы получаем в качестве ответа коды состояния «500», их нужно рассматривать как ошибки. Баги группируются по последовательности запросов, даже если она создает более одной ошибки в ходе фаззинга. Исключением является ситуация, в которой новая проблема была обнаружена из-за другого кода состояния, например, если первая ошибка была «500», а вторая – «503». Уникальные последовательности с запросами и ответами хранятся в файле bug_buckets.txt и имеют отдельный журнал.
Триаж багов
Помимо ошибок с кодом 500, фаззер умеет находить и другие типы багов. За это отвечают чекеры – специально подготовленные правила, анализирующие цепочку запросов и выявляющие события/уязвимости, на которые они были заточены.
Рассмотрим некоторые баги, которые нашлись с помощью чекеров. В файле InvalidDynamicObjectChecker_20x_1.replay.txt мы видим сначала POST-запрос, который создает пост в блоге. Затем наблюдаем GET-запрос, который пытается получить только что созданный пост. Можно заметить, что чекер недопустимых динамических объектов заменил идентификатор сообщения в блоге с 94 на 94?injected_query_string=123, а сервер это проигнорировал. Так как данный чекер предполагает, что любой недопустимый динамический объект должен вызывать ошибку (например, выдать код состояния ответа "HTTP 400 Bad Request", а не возвращать код 20x), он помечает это как баг.
В файле UseAfterFreeChecker_20x_1.replay.txt содержится POST-запрос на создание поста с id=108, затем DELETE-запрос на его удаление, а в конце GET-запрос на пост с id=108. Чекер «Use after free» выявил, что ресурс (в данном случае пост с id=108) был успешно удален, но к нему всё еще можно получить доступ. Это было помечено как баг.
Фаззинг (hardcore)
Теперь испытаем режим Fuzz. Для этого воспользуемся командой:
C:\restler_bin\restler\restler.exe fuzz --grammar_file C:\demo_server\Compile\grammar.py --dictionary_file C:\demo_server\Compile\dict.json --settings C:\demo_server\Compile\engine_settings.json --no_ssl --time_budget 1
Новый параметр time_budget – это продолжительность выполнения фаззинга в часах. Результаты тестирования находятся в новом каталоге Fuzz\RestlerResults\experiment{…}. Баги будут аналогичны FuzzLean, но если изучить файл logs\network.testing.{…}.1.txt, можно заметить еще несколько случаев, в которых возникали одинаковые ошибки. Однако фаззер сгруппировал их по запросам и не дублировал, как упоминалось выше. Так как это простейший API, Fuzz не смог обнаружить дополнительных багов.
Заключение
Сегодня мы поговорили о Stateful REST API-фаззинге с применением RESTler-а. В следующих статьях продемонстрируем возможности этого инструмента на более сложных API, а также покажем дополнительные возможности его настройки. Оставайтесь с нами!