Фаззинг на пальцах. Часть 2: автоматизация фаззинг-тестирования на примере ClusterFuzz

Это вторая часть цикла статей, посвященных фаззинг-тестированию, от сотрудников направления «Безопасная разработка» Центра кибербезопасности УЦСБ. У нас есть практический опыт проверки программ, и мы готовы делиться знаниями.

Первая часть цикла статей доступна по ссылке.

Введение

В предыдущей части мы ввели понятие фаззинга, сравнили подходы к проведению такого вида тестирования и рассказали про требования российского законодательства. В этой статье углубимся в технические особенности фаззинг-тестирования, затронем некоторые специализированные термины и расскажем про фаззинг-фермы.

Фаззинг-тестирование — уже неотъемлемая часть обеспечения безопасности приложений в современном цифровом мире. В условиях постоянно возрастающей угрозы кибератак и появления новых уязвимостей использование инновационных методов тестирования, таких как фаззинг, становится важным элементом стратегии обеспечения безопасности приложений.

С ростом сложности современных приложений и повышением требований к их безопасности, необходимость эффективного тестирования становится более острой. Фаззинг представляет уникальный подход, который позволяет автоматизировать процесс обнаружения уязвимостей, обеспечивая высокую степень покрытия кода и выявляя скрытые ошибки, которые могли бы остаться незамеченными при традиционных методах тестирования. Вместе с такими методами, как 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

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://: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 (для удаления входных данных, не увеличивающих покрытие кода).

410ae2ccd2befb178f0cf0b9d1bb339f.png

После нажатия кнопки ADD цель будет загружена в хранилище данных, а сервер назначит боту задачу на фаззинг.

Если на этом этапе возникает ошибка «Failed to upload» — обратитесь к разделу «Возможные ошибки» в конце статьи.

В логах бота можно наблюдать за процессом фаззинг-тестирования:

~/bot/clusterfuzz/bot/logs$ tail -f bot.log

f0db805f61df6a99f70d59e3fd2715ac.png

Некоторое время спустя в лог файлах появится stack trace и строка AddressSanitizer: heap-buffer-overflow.

5300f5f65f4253eff3475be34c275443.png

Еще через время на главной странице сервера (/testcases) появится информация о найденном дефекте: Heap-buffer-overflow READ{}.

3daa55cef42cf3d579859ccc8654fb10.png

Возможные проблемы

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= # 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 инженера. Переходите по ссылкам, чтобы узнать подробности и стать частью команды, которая проводит фаззинг-тестирования в крутых проектах и создает облачную платформу для анализа защищенности приложений.

© Habrahabr.ru