Это вторая часть цикла статей, посвященных фаззинг-тестированию, от сотрудников направления «Безопасная разработка» Центра кибербезопасности УЦСБ. У нас есть практический опыт проверки программ, и мы готовы делиться знаниями.
Первая часть цикла статей доступна по ссылке.
Введение
В предыдущей части мы ввели понятие фаззинга, сравнили подходы к проведению такого вида тестирования и рассказали про требования российского законодательства. В этой статье углубимся в технические особенности фаззинг-тестирования, затронем некоторые специализированные термины и расскажем про фаззинг-фермы.
Фаззинг-тестирование — уже неотъемлемая часть обеспечения безопасности приложений в современном цифровом мире. В условиях постоянно возрастающей угрозы кибератак и появления новых уязвимостей использование инновационных методов тестирования, таких как фаззинг, становится важным элементом стратегии обеспечения безопасности приложений.
С ростом сложности современных приложений и повышением требований к их безопасности, необходимость эффективного тестирования становится более острой. Фаззинг представляет уникальный подход, который позволяет автоматизировать процесс обнаружения уязвимостей, обеспечивая высокую степень покрытия кода и выявляя скрытые ошибки, которые могли бы остаться незамеченными при традиционных методах тестирования. Вместе с такими методами, как SAST, DAST и другими, фаззинг позволяет выявить различные классы уязвимостей.
Принцип работы фаззера
Фаззеры — это инструменты для проведения фаззинг-тестирования, которые автоматически генерируют большое количество входных данных с целью обнаружения уязвимостей в программном обеспечении. Вот краткое описание некоторых типов фаззеров:
1. Генерационные фаззеры (grammar-based)
принцип работы: используют грамматические правила для генерации более реалистичных тест-кейсов;
преимущества: генерация всех возможных состояний протокола или формата файла позволяет увеличить покрытие кода и вероятность обнаружения сложных ошибок;
недостатки: требуют предварительную настройку «грамматики», может занимать длительное время на неизвестных протоколах или форматах файла.
2. Мутационные фаззеры
принцип работы: исходные тестовые данные изменяются для создания новых вариантов;
преимущества: быстры в настройке;
недостатки: могут не обнаружить более сложные уязвимости, требующие значительных изменений данных, фактически никогда не воспроизведут все состояния протокола или формата файла.
В настоящее время мутационный фаззинг находится в центре внимания. AFL (American Fuzzy Lop) сыграл ключевую роль в развитии фаззинг-тестирования благодаря своему эффективному механизму мутации тестовых данных. Также AFL одним из первых начал применять подход со сбором покрытия кода.
Каждый тип фаззера имеет свои преимущества и ограничения, и выбор зависит от конкретных требований тестирования и характеристик целевого приложения. Иногда комбинированный подход, использующий несколько видов фаззеров, может быть наиболее эффективным для обнаружения разнообразных уязвимостей.
Инструментация программного кода
Чтобы получать обратную связь по тестовым данным от объекта оценки, над ним проводят процедуру инструментации. Инструментация программного обеспечения в контексте фаззинга полезна для сбора информации о том, какие части программы были достигнуты в процессе тестирования, что в свою очередь помогает определить эффективность всего тестирования.
Процесс инструментации включает в себя добавление дополнительных инструкций в исходный код программы. Этот код предназначен для сбора информации о процессе выполнения программы, отслеживания определенных событий.
Говоря про инструментацию программного обеспечения в рамках фаззинга, необходимо упомянуть еще про санитайзеры. Санитайзеры — это инструменты, которые помогают выявлять ошибки в программном коде: начиная от переполнения буфера, заканчивая гонкой потоков. Они инструментируют программный код, позволяя отслеживать, например, операции с памятью в режиме выполнения.
Таким образом, использование санитайзеров в процессе фаззинга даёт возможность обнаруживать новые программные ошибки и собирать дополнительные сведения для их анализа.
Фаззинг-фермы
Фаззинг-ферма представляет собой распределенную систему, в которой несколько экземпляров фаззеров выполняют тестирование приложений на уязвимости параллельно. Этот подход приносит несколько преимуществ:
Увеличение покрытия кода
Фаззинг-ферма способствует более широкому охвату возможных уязвимостей. За счет параллельного выполнения тестов на различных экземплярах фаззеров, система позволяет сформировать больше сценариев использования и обнаружить уязвимости, которые были бы проигнорированы одним инструментом.
Ускорение процесса тестирования
Параллельное выполнение тестовых задач на фаззере позволяет ускорить процесс обнаружения уязвимостей. Фаззинг-ферма может значительно сократить время, необходимое для выполнения тестов, что особенно важно в условиях ограниченных сроков разработки.
Работа с большими объемами данных
Фаззинг-фермы способны обрабатывать большие объемы тестовых данных, что повышает вероятность обнаружения различных классов уязвимостей. Это особенно полезно при тестировании крупных и сложных приложений.
Легкость масштабирования
Фаззинг-фермы легко масштабируются, позволяя добавлять новые экземпляры фаззеров для обработки дополнительных тестов или расширения тестового покрытия при необходимости.
Автоматизация и сбор результатов
Фаззинг-фермы обеспечивают автоматизированное тестирование и сбор результатов. Это облегчает управление процессом и анализ полученных данных.
Пример конкретной фаззинг-фермы — Clustefuzz.
ClusterFuzz — это открытое программное обеспечение для автоматизированного фаззинг-тестирования, разработанное Google. Его основное предназначение — обеспечение безопасности и стабильности программного обеспечения через обнаружение и устранение уязвимостей.
ClusterFuzz имеет ряд преимуществ, среди которых автоматизированный процесс фаззинг-тестирования, масштабируемость, интеграция с CI/CD, анализ результатов.
Google предоставляет инструкции по развертыванию и использованию этого инструмента в облаке Google Cloud Platform, однако инструкции по развертыванию фаззинг-фермы на локальном распределенном вычислительном кластере нет. В связи с этим мы решили разработать инструкцию для локальной конфигурации ClusterFuzz на единственном узле.
Установка ClusterFuzz
Клонирование репозитория
~$ git clone https://github.com/google/clusterfuzz.git
Установка зависимостей
Установка golang
Установим с помощью менеджера пакетов:
$ sudo apt install golang
Установка зависимостей скриптом
~/clusterfuzz$ local/install_deps.bash
Для явного указания версии Python следует использовать переменную окружения: $ PYTHON=python3.7 ./local/install_deps.bash
Проверка работоспособности
Активируем виртуальное окружение со всеми зависимостями:
~/clusterfuzz$ pipenv shell
Запустим главный скрипт, посредством которого происходит управление clustefuzz:
~/clusterfuzz$ python3 butler.py --help
usage: butler.py [-h]
{bootstrap,py_unittest,js_unittest,format,lint,package,deploy,run_server,run,run_bot,remote,clean_indexes,create_config,integration_tests}
...
Butler is here to help you with command-line tasks.
positional arguments:
{bootstrap,py_unittest,js_unittest,format,lint,package,deploy,run_server,run,run_bot,remote,clean_indexes,create_config,integration_tests}
bootstrap Install all required dependencies for running an appengine, a bot,and a mapreduce locally.
py_unittest Run Python unit tests.
js_unittest Run Javascript unit tests.
format Format changed code in current branch.
lint Lint changed code in current branch.
package Package clusterfuzz with a staging revision
deploy Deploy to Appengine
run_server Run the local Clusterfuzz server.
run Run a one-off script against a datastore (e.g. migration).
run_bot Run a local clusterfuzz bot.
remote Run command-line tasks on a remote bot.
clean_indexes Clean up undefined indexes (in index.yaml).
create_config Create a new deployment config.
integration_tests Run end-to-end integration tests.
optional arguments:
-h, --help show this help message and exit
Ошибок нет, значит, все необходимые модули были установлены и подгружены.
Для большей уверенности можно запустить тесты:
~/clusterfuzz$ python3 butler.py py_unittest -t appengine
...
----------------------------------------------------------------------
Ran 609 tests in 72.576s
OK (skipped=9)
Если на этом этапе возникает ошибка, связанная с версией Python, обратитесь к разделу "Возможные ошибки" в конце статьи.
Инициализация сервера
Первый запуск сервера
~/clusterfuzz$ python3 butler.py run_server --bootstrap
Строка `[INFO] Listening at: http://0.0.0.0:9000 (5397)` свидетельствует о том, что сервер успешно запущен. С этого момента появляется возможность зайти в веб-интерфейс сервера по указанному адресу.
Сервер помимо порта 9000 использует и другие порты (9004, 9008, 9009), поэтому убедитесь, что они не заняты другими сервисами и не блокируются межсетевым экраном, либо поменяйте их на другие в файле clusterfuzz/src/local/butler/constants.py
Запуск локального фаззинг-бота
Запустим локальное окружение:
$ pipenv shell
Запустим бота, указав директорию его размещения:
~/clusterfuzz$ python3 butler.py run_bot ../bot
Бот логгирует свою активность в файле bot.log:
~/bot/clusterfuzz/bot/logs$ tail bot.log
2024-02-07 08:29:08,860 - run_bot - INFO - Using local source, skipping source code update.
2024-02-07 08:29:08,860 - run_bot - INFO - Running platform initialization scripts.
2024-02-07 08:29:09,366 - run_bot - INFO - Completed running platform initialization scripts.
2024-02-07 08:29:09,678 - run_bot - ERROR - Failed to get any fuzzing tasks. This should not happen.
NoneType: None
Ошибка в последней строке возникает из-за того, что у бота нет назначенных ему задач.
На этом этапе разворачивание локального экземпляра ClusterFuzz окончено.
Подготовка фаззинг-цели
Требования
libFuzzer и AFL используют инструментацию компилятора Clang. Требуется использовать Clang версии 6.0 или выше.
Установим компилятор через apt:
~$ sudo apt install clang
~$ clang --version
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Исходный код
Покажем, как обнаружить уязвимость Heartbleed с помощью libFuzzer и ClusterFuzz.
Heartbleed — уязвимость в OpenSSL 1.0.1, обнаруженная в 2014 году. Возникает из-за недостаточной валидации ввода, в результате чего у потенциального нарушителя появляется возможность читать произвольные участки оперативной памяти сервера, включая конфиденциальные данные (ключи, пароли и т. д.).
Сначала скачаем уязвимую версию OpenSSL:
~$ curl -O https://www.openssl.org/source/old/1.0.1/openssl-1.0.1f.tar.gz && tar xf openssl-1.0.1f.tar.gz
Подготовка к сборке:
~$ cd openssl-1.0.1f/ && ./config
Сборка уязвимой версии OpenSSL:
~/openssl-1.0.1f$ make CC="clang -g -fsanitize=address,fuzzer-no-link"
Скачиваем фаззинг-цель и подготовленный сертификат:
$ curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/handshake-fuzzer.cc
$ curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.key
$ curl -O
Сборка фаззинг-цели
Запустим сборку тестируемого исполняемого файла с инструментацией с помощью clang++:З
~$ clang++ -g handshake-fuzzer.cc -fsanitize=address,fuzzer openssl-1.0.1f/libssl.a openssl-1.0.1f/libcrypto.a -std=c++17 -Iopenssl-1.0.1f/include/ -lstdc++fs -ldl -lstdc++ -o handshake-fuzzer
Добавим скомпилированную цель и сертификат в архив для загрузки на сервер:
~$ zip openssl-fuzzer-build.zip handshake-fuzzer server.key server.pem
Фаззинг
Создание задачи
Для создания задачи необходимо открыть страницу по адресу http://<server_ip>:9000/jobs и найти форму под названием "ADD NEW JOB".
Заполним необходимые поля:
Name: libfuzzer_asan_linux_openssl
Platform: Linux
Select/modify fuzzers: libFuzzer
Description: heartbleed example
Templates: engine_asan и libfuzzer
Custom Build: загрузить zip архив, сформированный в предыдущем разделе
Environment: CORPUS_PRUNE=True (для удаления входных данных, не увеличивающих покрытие кода).
После нажатия кнопки ADD цель будет загружена в хранилище данных, а сервер назначит боту задачу на фаззинг.
Если на этом этапе возникает ошибка "Failed to upload" — обратитесь к разделу "Возможные ошибки" в конце статьи.
В логах бота можно наблюдать за процессом фаззинг-тестирования:
~/bot/clusterfuzz/bot/logs$ tail -f bot.log
Некоторое время спустя в лог файлах появится stack trace и строка AddressSanitizer: heap-buffer-overflow.
Еще через время на главной странице сервера (/testcases) появится информация о найденном дефекте: Heap-buffer-overflow READ{}.
Возможные проблемы
Downgrade to python3.7
Clusterfuzz требует Python3.7. По умолчанию в Ubuntu 20.04 предустановлен Python3.8, поэтому в случае ошибок с версией Python необходимо установить более раннюю версию. Это можно сделать, собирая Python из исходного кода, устанавливая из PPA репозитория deadsnakes или с помощью менеджера версий asdf. Опишем установку последним способом.
Установка asdf
Клонируем репозиторий:
~$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 # 0.14.0 - самая актуальная версия на момент написания руководства
Устанавливаем asdf:
~$ echo '. "$HOME/.asdf/asdf.sh"' >> .bashrc
~$ echo '. "$HOME/.asdf/completions/asdf.bash"' >> .bashrc
Перезапускаем оболочку и проверяем, что всё установилось:
~$ asdf --version
v0.14.0-ccdd47d
Установка Python3.7
Устанавливаем Python плагин:
~$ asdf plugin-add python
Устанавливаем пакеты, необходимые для сборки Python:
sudo apt-get install curl gcc libbz2-dev libev-dev libffi-dev libgdbm-dev liblzma-dev libncurses-dev libreadline-dev libsqlite3-dev libssl-dev make tk-dev wget zlib1g-dev
Устанавливаем Python3.7.4:
~$ asdf install python 3.7.4
Активируем Python3.7.4:
~$ asdf global python 3.7.4
Проверяем версию:
~$ python3 --version
Python 3.7.4
Failed to upload. (user@localhost)
Эта ошибка может возникать при попытке создания fuzzing job.
Все объекты должны загружаться в Google Cloud Storage, поэтому при локальном запуске ClusterFuzz хранилище данных эмулируется. За это отвечает local/emulators/gcs.go. По умолчанию эмулятор запускается на localhost:9008. Для решения проблемы необходимо поменять адрес прослушивания на 0.0.0.0:9008:
~/clusterfuzz$ sed -i 's/localhost:%d/0.0.0.0:%d/g' local/emulators/gcs.go
Осталось указать новый адрес эмулируемого Google Cloud Storage нашему серверу:
~$ SERVER_IP=<your-server-ip> # e.g. 192.168.1.2
~/clusterfuzz$ sed -i 's|http://localhost|http://'"$SERVER_IP"'|g' src/local/butler/constants.py
Полезные обсуждения
Google не предоставляет инструкцию по разворачиванию ClusterFuzz на распределенном локальном вычислительном кластере, однако в официальном репозитории проекта на GitHub есть несколько обсуждений на эту тему, где пользователи делятся своими идеями:
Заключение
ClusterFuzz представляет собой мощный инструмент для автоматизации фаззинг-тестирования, который обеспечивает эффективное обнаружение уязвимостей в программном обеспечении. В этой статье мы рассмотрели процесс локального развертывания ClusterFuzz на одном вычислительном узле, обращая внимание на практические шаги и преимущества подхода.
Фаззинг-тестирование становятся неотъемлемой частью стратегии обеспечения безопасности приложений. Использование фаззинга позволяет выявлять уязвимости, которые могли бы остаться незамеченными при традиционных методах тестирования. ClusterFuzz делает этот процесс еще более эффективным и удобным, открывая новые возможности для повышения уровня безопасности в мире программного обеспечения.
Автор: Роман Корнилов, аналитик направления безопасной разработки УЦСБ
В направлении безопасной разработки УЦСБ открыты вакансии ведущего системного аналитика, DevSecOps инженера, AppSec инженера. Переходите по ссылкам, чтобы узнать подробности и стать частью команды, которая проводит фаззинг-тестирования в крутых проектах и создает облачную платформу для анализа защищенности приложений.