Фаззинг на пальцах. Часть 2: автоматизация фаззинг-тестирования на примере 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 https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.pem
Сборка фаззинг-цели
Запустим сборку тестируемого исполняемого файла с инструментацией с помощью 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 (для удаления входных данных, не увеличивающих покрытие кода).
Установка 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