-
Notifications
You must be signed in to change notification settings - Fork 32
Fuzzing fast‐xml‐parser (JavaScript) project with sydr‐fuzz (Jazzer.js backend) (rus)
В этой статье будет мы рассмотрим подход к фаззингу приложений на языке JavaScript с помощью интерфейса Sydr-Fuzz на основе фаззера Jazzer.js. Sydr-Fuzz предоставляет удобный интерфейс для запуска гибридного фаззинга, задействуя возможности динамического символьного выполнения на базе инструмента Sydr в сочетании с современными фаззерами libFuzzer и AFLplusplus. Помимо фаззинга, Sydr-Fuzz предлагает набор возможностей для минимизации корпуса, сбора покрытия, поиска ошибок в программах посредством проверки предикатов безопасности, а также анализа аварийных завершений при помощи Casr. Помимо программ на чисто компилируемых языках, Sydr-Fuzz поддерживает фаззинг приложений на языках Python и Java. Следующим шагом в развитии инструмента стало добавление возможности фаззинга JavaScript кода. Для реализации этой идеи был выбран популярный внутрипроцессный фаззер с обратной связью по покрытию Jazzer.js, основанный на libFuzzer. Он подходит для проектов на платформе Node.js и был разработан Code Intelligence аналогично фаззеру Jazzer для языка Java. Хотя мы и не сможем использовать возможности динамического символьного выполнения для проектов на JavaScript, вся остальная функциональность Sydr-Fuzz будет нам доступна.
Для демонстрации возможностей фаззинга JavaScript кода посредством Sydr-Fuzz будем использовать проект fast-xml-parser. Инструкция по установке и использованию фаззера Jazzer.js может быть найдена в его репозитории на GitHub, а репозиторий OSS-Fuzz предоставляет также подробную инструкцию по подготовке проекта на языке JavaScript к фаззингу и настройке необходимого окружения. В нашем репозитории OSS-Sydr-Fuzz уже есть готовый Docker-контейнер с настроенным окружением, который мы и будем использовать для фаззинга.
Сборка и запуск докер-контейнера производится следующей командой:
$ docker build -t oss-sydr-fuzz-fastxmlparser .
$ docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v /etc/localtime:/etc/localtime:ro --rm -it -v $PWD:/jazzer.js/fuzz oss-sydr-fuzz-fastxmlparser /bin/bash
Здесь следует обратить внимание на то, что в отличие от остальных проектов, мы монтируем нашу рабочую директорию фаззинга ($PWD
) не в корень, а в /jazzer.js
. Для запуска фаззинга с помощью jazzer.js, фаззер должен быть установлен в одной из родительских директорий. В директории /jazzer.js
установлен фаззер, поэтому запуск фаззинга и остальных команд булет происходить из /jazzer.js/fuzz
.
Перейдем к рассмотрению фаззинг-цели:
const { FuzzedDataProvider } = require('@jazzer.js/core');
const XMLParser = require('./src/xmlparser/XMLParser');
const XMLBuilder = require('./src/xmlbuilder/json2xml');
const XMLValidator = require('./src/fxp').XMLValidator;
module.exports.fuzz = function(data) {
try {
const provider = new FuzzedDataProvider(data);
const xmlString = provider.consumeString(1024);
const parser = new XMLParser();
let jObj = parser.parse(xmlString);
let builder = new XMLBuilder();
const xmlContent = builder.build(jObj);
XMLValidator.validate(xmlContent, {
allowBooleanAttributes: true,
});
} catch (error) {
if (!ignoredError(error)) throw error;
}
};
function ignoredError(error) {
return !!ignored.find((message) => error.message.indexOf(message) !== -1);
}
const ignored = ['Cannot read properties', 'is not closed', 'Invalid Tag'];
В файле фаззинг-цели нам необходимо экспортировать функцию fuzz
: module.exports.fuzz = function(data) {...}
, принимающую на вход один параметр типа Buffer
. Jazzer.js также предоставляет возможность использования FuzzedDataProvider для перевода байтового представления входных данных в типы целевого языка: const { FuzzedDataProvider } = require('@jazzer.js/core');
. Некоторые исключения, выбрасываемые целевой библиотекой fast-xml-parser, могут быть намеренно проигнорированы, в случае если они соответствуют некритичным ошибкам при использовании библиотеки. Такие исключения определяются функцией ignoredError
в нашем примере.
Сборка проекта для фаззинга не требует специальных действий, кроме установки зависимостей и клонирования самого проекта, что уже описано в соответствующем Dockerfile. Теперь мы можем собрать наш Docker-контейнер и перейти к фаззингу!
Взглянем на конфигурационный файл xml.toml:
exit-on-time = 3600
[jazzer_js]
path = "/jazzer.js/fast-xml-parser/fuzz.js"
args = "/corpus --sync -- -workers=2 -jobs=100 -dict=/xml.dict"
Здесь мы видим опциональный параметр exit-on-time
, который задаёт время до завершения фаззинга при отсутствии нового покрытия. В таблице также указаны путь до фаззинг-цели path
и аргументы args
для фаззинг-движков Jazzer.js (располагаются до разделителя --
) и libFuzzer (располагаются после разделителя --
).
Особое внимание следует обратить на опцию --sync
, которая в некоторых случаях позволяет увеличить производительность фаззинга. Jazzer.js поддерживает фаззинг целей с асинхронными функциями, однако для этого требуется дополнительная синхронизация между Node.js и потоком фаззинга, что снижает скорость анализа. Несмотря на это, режим фаззинга для асинхронных функций используется в Jazzer.js по умолчанию, поскольку он поддерживает анализ всех фаззинг-целей и с синхронными вычислениеми, и с асинхронными. Если ваша фаззинг-цель состоит из исключительно синхронного кода, то скорость фаззинга можно улучшить, переключив режим фаззинга с помощью --sync
. Подробнее об этом в можно почитать в документации Jazzer.js.
Запустим фаззинг следующей командой:
# sydr-fuzz -c xml.toml run
[2024-01-25 18:35:48] [INFO] #186 INITED cov: 568 ft: 2817 corp: 180/7703b exec/s: 0 rss: 148Mb
[2024-01-25 18:35:48] [INFO] #199 NEW cov: 568 ft: 2818 corp: 181/7717b lim: 527 exec/s: 0 rss: 148Mb L: 14/527 MS: 3 InsertByte-ShuffleBytes-CopyPart-
[2024-01-25 18:35:48] [INFO] #207 NEW cov: 568 ft: 2819 corp: 182/7754b lim: 527 exec/s: 0 rss: 148Mb L: 37/527 MS: 3 CopyPart-ShuffleBytes-ChangeBinInt-
[2024-01-25 18:35:48] [INFO] #209 NEW cov: 568 ft: 2822 corp: 183/7869b lim: 527 exec/s: 0 rss: 148Mb L: 115/527 MS: 2 CMP-InsertRepeatedBytes- DE: "\001\000\000\000\000\000\000\000"-
[2024-01-25 18:35:48] [INFO] #210 NEW cov: 568 ft: 2825 corp: 184/7969b lim: 527 exec/s: 0 rss: 148Mb L: 100/527 MS: 1 PersAutoDict- DE: "\001\000\000\000\000\000\000\000"-
[2024-01-25 18:35:48] [INFO] #223 NEW cov: 568 ft: 2826 corp: 185/7987b lim: 527 exec/s: 0 rss: 148Mb L: 18/527 MS: 3 ManualDict-InsertByte-EraseBytes- DE: "<![IGNORE["-
[2024-01-25 18:35:48] [INFO] #251 NEW cov: 568 ft: 2834 corp: 186/7991b lim: 527 exec/s: 0 rss: 148Mb L: 4/527 MS: 3 CopyPart-ChangeBit-InsertByte-
[2024-01-25 18:35:48] [INFO] #258 NEW cov: 568 ft: 2843 corp: 187/8177b lim: 527 exec/s: 0 rss: 148Mb L: 186/527 MS: 2 ChangeByte-CrossOver-
[2024-01-25 18:35:48] [INFO] #269 NEW cov: 568 ft: 2862 corp: 188/8190b lim: 527 exec/s: 0 rss: 148Mb L: 13/527 MS: 1 ManualDict- DE: "#PCDATA"-
[2024-01-25 18:35:48] [INFO] #270 NEW cov: 568 ft: 2865 corp: 189/8244b lim: 527 exec/s: 0 rss: 148Mb L: 54/527 MS: 1 ShuffleBytes-
[2024-01-25 18:35:48] [INFO] ==1593== Uncaught Exception: Error: Invalid entity name g /jazzer.js/fuzz/xml-out/crashes/crash-f4be71632a88b6db3c44fd1070cfaa4ea9b8806b
[2024-01-25 18:36:29] [INFO] ==2948== Uncaught Exception: Error: Unclosed DOCTYPE /jazzer.js/fuzz/xml-out/crashes/crash-17e68caf797dea78d2c4ed08a96818e2fbb91db0
[2024-01-25 18:36:29] [INFO] ==2957== Uncaught Exception: Error: Unclosed DOCTYPE /jazzer.js/fuzz/xml-out/crashes/crash-a10dff7915f5d028effd802258cfb8938728408c
[2024-01-25 18:36:29] [INFO] ==2928== Uncaught Exception: Error: Invalid entity name goKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK,KKKKKKKKKNOTATIONKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK /jazzer.js/fuzz/xml-out/crashes/crash-0c802f19059df64ed1efcf4a662397bbe08cc80e
[2024-01-25 18:36:29] [INFO] ==2901== Uncaught Exception: Error: Unclosed DOCTYPE /jazzer.js/fuzz/xml-out/crashes/crash-4a420f2d637d310cf7d461b658f45f376c481074
[2024-01-25 18:36:29] [INFO] ==2976== Uncaught Exception: Error: Unclosed DOCTYPE /jazzer.js/fuzz/xml-out/crashes/crash-2210bcf04f3535379be3953e84002c10f1341363
[2024-01-25 18:36:30] [INFO] [RESULTS] Fuzzing corpus is saved in /jazzer.js/fuzz/xml-out/corpus
[2024-01-25 18:36:30] [INFO] [RESULTS] oom/leak/timeout/crash: 0/0/0/100
[2024-01-25 18:36:30] [INFO] [RESULTS] Fuzzing results are saved in /jazzer.js/fuzz/xml-out/crashes
После завершения фаззинга мы получили 100 аварийных завершений. Применение Casr для их сортировки будет как нельзя кстати. Однако перед этим минимизируем полученный корпус:
# sydr-fuzz -c xml.toml cmin
[2024-01-25 18:39:09] [INFO] Original fuzzing corpus saved as /jazzer.js/fuzz/xml-out/corpus-old
[2024-01-25 18:39:09] [INFO] Minimizing corpus /jazzer.js/fuzz/xml-out/corpus
[2024-01-25 18:39:09] [INFO] Jazzer.js environment: ASAN_OPTIONS=abort_on_error=1,allocator_may_return_null=1,detect_leaks=0,hard_rss_limit_mb=0,malloc_context_size=0
[2024-01-25 18:39:09] [INFO] Launching Jazzer.js: cd "/jazzer.js/fuzz/xml-out/jazzer.js" && ASAN_OPTIONS="abort_on_error=1,allocator_may_return_null=1,detect_leaks=0,hard_rss_limit_mb=0,malloc_context_size=0" "/jazzer.js/fast-xml-parser/fuzz.js" "--sync" "/jazzer.js/fuzz/xml-out/corpus" "/jazzer.js/fuzz/xml-out/corpus-old" "--" "-merge=1" "-rss_limit_mb=8192" "-detect_leaks=0" "-artifact_prefix=/jazzer.js/fuzz/xml-out/crashes/" "-use_value_profile=1" "-verbosity=2"
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: 641 files, 0 in the initial corpus, 0 processed earlier
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: attempt 1
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: successful in 1 attempt(s)
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: the control file has 89857 bytes
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: consumed 0Mb (148Mb rss) to parse the control file
[2024-01-25 18:39:11] [INFO] MERGE-OUTER: 400 new files with 3908 new features added; 606 new coverage edges
Мы смогли значительно сократить размер входного корпуса: с 641 до 400 файлов. Хорошая работа! Теперь посмотрим на достигнутое покрытие.
Для сбора покрытия будем использовать интерфейс, предоставленный самим фаззером Jazzer.js. Для этого воспользуемся командой sydr-fuzz jscov
. Получим отчёт о покрытии в формате html:
# sydr-fuzz -c xml.toml jscov html
[2024-01-25 18:41:53] [INFO] Running jscov html "/jazzer.js/fuzz/xml.toml"
[2024-01-25 18:41:53] [INFO] Collecting coverage data for each file in corpus: /jazzer.js/fuzz/xml-out/corpus
[2024-01-25 18:41:53] [INFO] Jazzer.js environment: ASAN_OPTIONS=abort_on_error=1,allocator_may_return_null=1,detect_leaks=0,hard_rss_limit_mb=0,malloc_context_size=0
[2024-01-25 18:41:53] [INFO] Launching Jazzer.js: cd "/jazzer.js/fuzz/xml-out/jazzer.js" && ASAN_OPTIONS="abort_on_error=1,allocator_may_return_null=1,detect_leaks=0,hard_rss_limit_mb=0,malloc_context_size=0" "/jazzer.js/fast-xml-parser/fuzz.js" "--sync" "--cov" "-m regression" "--cov_dir=/jazzer.js/fuzz/xml-out/coverage/html" "--cov_reporters=html" "/jazzer.js/fuzz/xml-out/corpus" "--" "-detect_leaks=0" "-artifact_prefix=/jazzer.js/fuzz/xml-out/crashes/" "-use_value_profile=1" "-verbosity=2" "-dict=/xml.dict" "-runs=0"
[2024-01-25 18:41:54] [INFO] html coverage report is saved to "/jazzer.js/fuzz/xml-out/coverage/html"
Покрытие по исходному коду будет выглядеть следующим образом:
В конце нашего пайплайна анализа проекта применим Casr для анализа и сортировки аварийных завершений:
# sydr-fuzz -c xml.toml casr
Вы можете узнать больше о Casr из репозитория Casr или из другого гайда.
Посмотрим на вывод команды:
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Deduplicating CASR reports...
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Number of reports before deduplication: 100. Number of reports after deduplication: 3
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Clustering CASR reports...
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Number of clusters: 3
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] ==> <cl1>
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Crash: /jazzer.js/fuzz/xml-out/casr/cl1/crash-021a3f5bf89df1c650bbffc4d028737d8bfc0f4d
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] casrep: NOT_EXPLOITABLE: Error: /jazzer.js/fast-xml-parser/src/xmlparser/DocTypeReader.js:56:19
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Similar crashes: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Cluster summary -> Error: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] ==> <cl2>
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Crash: /jazzer.js/fuzz/xml-out/casr/cl2/crash-0c802f19059df64ed1efcf4a662397bbe08cc80e
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] casrep: NOT_EXPLOITABLE: Error: /jazzer.js/fast-xml-parser/src/xmlparser/DocTypeReader.js:149:15
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Similar crashes: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Cluster summary -> Error: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] ==> <cl3>
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Crash: /jazzer.js/fuzz/xml-out/casr/cl3/crash-45b9da6dab82407a6cf96eec2bb232386cd6360e
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] casrep: NOT_EXPLOITABLE: Error: /jazzer.js/fast-xml-parser/src/xmlparser/DocTypeReader.js:82:46
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Similar crashes: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] Cluster summary -> Error: 1
[2024-01-25 18:57:03] [INFO] [CASR-LIBFUZZER] SUMMARY -> Error: 3
[2024-01-25 18:57:03] [INFO] Crashes and Casr reports are saved in /jazzer.js/fuzz/xml-out/casr
В результате работы Casr у нас осталось всего лишь 3 креша, распределённых по разным кластерам. Теперь их очень легко просмотреть в ручном режиме. Посмотрим, например, на отчёт из второго кластера:
Получили необработанное исключение, вызванное неверным именем сущности. Выглядит как потенциальная ошибка!
В этой статье был рассмотрен подход к фаззингу приложений на языке JavaScript с помощью интерфейса Sydr-Fuzz. Запуск фаззинга, минимизация входного корпуса, сбор покрытия, анализ аварийных завершений - всё это можно быстро и удобно запустить с использованием Sydr-Fuzz. А инструмент Casr успешно помогает справляться с большим количеством аварийных завершений и предоставляет информацию о них в удобном виде.