Skip to content

Fuzzing json‐sanitizer (Java) project with sydr‐fuzz (Jazzer backend) (rus)

Savidov Georgy edited this page Jul 18, 2023 · 1 revision

Введение

В этой небольшой статье будет продемонстрирован подход к фаззингу ПО, написанного на языке Java, с помощью sydr-fuzz с поддержкой Jazzer . Sydr-fuzz совмещает динамическое символьное выполнение Sydr с современными фаззерами, такими как libFuzzer и AFLplusplus. Инструмент предоставляет удобный интерфейс для минимизации корпуса, проверки предикатов безопасности, сбора покрытия и анализа аварийных завершений с помощью casr. Также существует возможность фаззинга Python кода с помощью Atheris. Было решено добавить в нашу систему фаззинг Java-кода. С этим нам поможет Jazzer — внутрипроцессный фаззер с обратной связью по покрытию на базе libFuzzer для платформы JVM, разработанный Code Intelligence. Мы не сможем использовать Sydr для Java-кода, однако все остальные полезные для анализа возможности sydr-fuzz будут нам доступны.

Подготовка фаззинг-цели

В качестве демонстрационного проекта для фаззинга возьмем json-sanitizer. Для фаззинга Java-кода нам будет необходим Jazzer. Инструкция по установке Jazzer может быть найдена в его репозитории. Понадобится сам бинарный файл jazzer и его библиотека jazzer_standalone_deploy.jar. Подробная инструкция по сборке Java проектов и подготовки их к фаззингу может быть также найдена на oss-fuzz. Docker контейнер со всем необходимым окружением для фаззинга находится на странце проекта в oss-sydr-fuzz. Давайте посмотрим на фаззинг-цель:

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
import com.google.json.JsonSanitizer;

public class DenylistFuzzer {
  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
    String input = data.consumeRemainingAsString();
    String output;
    try {
      output = JsonSanitizer.sanitize(input, 10);
    } catch (ArrayIndexOutOfBoundsException e) {
      // ArrayIndexOutOfBoundsException is expected if nesting depth is
      // exceeded.
      return;
    }

    // Check for forbidden substrings. As these would enable Cross-Site
    // Scripting, treat every finding as a high severity vulnerability.
    assert !output.contains("</script")
        : new FuzzerSecurityIssueHigh("Output contains </script");
    assert !output.contains("]]>")
        : new FuzzerSecurityIssueHigh("Output contains ]]>");

    // Check for more forbidden substrings. As these would not directly enable
    // Cross-Site Scripting in general, but may impact script execution on the
    // embedding page, treat each finding as a medium severity vulnerability.
    assert !output.contains("<script")
        : new FuzzerSecurityIssueMedium("Output contains <script");
    assert !output.contains("<!--")
        : new FuzzerSecurityIssueMedium("Output contains <!--");
  }
}

Нам нужно создать файл с названием *Fuzzer.java, в котором будет реализован Java-класс с тем же названием. В классе должен быть реализован статический метод fuzzerTestOneInput(byte[]) или fuzzerTestOneInput(FuzzedDataProvider). Будем использовать последний для удобства представления типов Java.

Для сборки json-sanitizer необходимо установить maven:

# wget https://dlcdn.apache.org/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.tar.gz
# tar -xvf apache-maven-*-bin.tar.gz
# rm apache-maven-*-bin.tar.gz
# mv apache-maven-* /opt/
# export PATH="$PATH:/opt/apache-maven-3.9.3/bin"

И в директории самого проекта выполнить команду:

# mvn package

Теперь осталось лишь собрать фаззинг-цель:

# javac -cp /path/to/target/dir:/json-sanitizer/target/json-sanitizer-<version>.jar:/path/to/jazzer_standalone_deploy.jar DenylistFuzzer

Мы готовы начинать фаззинг!

Фаззинг

Перед началом фаззинга взглянем на конфигурационный файл DenylistFuzzer.toml:

[jazzer]
target_class = "DenylistFuzzer"
args = "--cp=/out/:/out/json-sanitizer.jar -jobs=400 -workers=4 -rss_limit_mb=4096 /out/corpus -dict=/out/DenylistFuzzer.dict"

С помощью поля target_class мы задаем класс с методом fuzzerTestOneInput. В аргументах необходимо указать --cp (class path) - пути до всех зависимостей нашей фаззинг-цели. Также тут можно указать как остальные опции самого Jazzer'a, так и опции libFuzzer'а.

Давайте начнем фаззинг:

# sydr-fuzz -c DenylistFuzzer.toml run
[2023-07-12 13:17:41] [INFO] #12792     INITED cov: 470 ft: 6683 corp: 2041/10117Kb exec/s: 2132 rss: 966Mb
[2023-07-12 13:17:41] [INFO] #12988     REDUCE cov: 470 ft: 6683 corp: 2041/10117Kb lim: 131336 exec/s: 2164 rss: 966Mb L: 36/131336 MS: 2 EraseBytes-Custom-
[2023-07-12 13:17:41] [INFO] #13214     REDUCE cov: 470 ft: 6683 corp: 2041/10117Kb lim: 131336 exec/s: 2202 rss: 966Mb L: 31/131336 MS: 2 EraseBytes-Custom-
[2023-07-12 13:17:41] [INFO] #13220     REDUCE cov: 470 ft: 6683 corp: 2041/10117Kb lim: 131336 exec/s: 2203 rss: 966Mb L: 27/131336 MS: 2 EraseBytes-Custom-
[2023-07-12 13:17:41] [INFO] #13350     REDUCE cov: 470 ft: 6683 corp: 2041/10117Kb lim: 131336 exec/s: 2225 rss: 966Mb L: 35/131336 MS: 10 CopyPart-Custom-ChangeBinInt-Custom-CMP-Custom-ChangeByte-Custom-EraseBytes-Custom- DE: "A58e5"-
[2023-07-12 13:17:41] [INFO] #13496     REDUCE cov: 470 ft: 6683 corp: 2041/10117Kb lim: 131336 exec/s: 2249 rss: 966Mb L: 68/131336 MS: 2 EraseBytes-Custom-
[2023-07-12 13:17:41] [INFO] #13656     REDUCE cov: 470 ft: 6683 corp: 2041/10108Kb lim: 131336 exec/s: 2276 rss: 966Mb L: 118030/131336 MS: 10 CrossOver-Custom-InsertByte-Custom-CrossOver-Custom-InsertRepeatedBytes-Custom-EraseBytes-Custom-
[2023-07-12 13:17:42] [INFO] #13978     REDUCE cov: 470 ft: 6683 corp: 2041/10108Kb lim: 131336 exec/s: 2329 rss: 966Mb L: 10723/131336 MS: 4 CrossOver-Custom-EraseBytes-Custom-
[2023-07-12 13:17:42] [INFO] == Java Exception: java.lang.IndexOutOfBoundsException: start 25, end 1, length 26 /fuzz/DenylistFuzzer-out/crashes/crash-abce4e6aa46f9c6d172cd241fb4d1e30c21e7763
[2023-07-12 13:58:57] [INFO] #24412	INITED cov: 470 ft: 6943 corp: 2118/9924Kb exec/s: 2034 rss: 994Mb
[2023-07-12 13:58:57] [INFO] #24483	REDUCE cov: 470 ft: 6943 corp: 2118/9924Kb lim: 131336 exec/s: 2040 rss: 994Mb L: 3/131336 MS: 2 EraseBytes-Custom-
[2023-07-12 13:58:59] [INFO] == Java Exception: java.lang.IndexOutOfBoundsException: start 27, end 0, length 27 /fuzz/DenylistFuzzer-out/crashes/crash-f69217eecbb509cfc6dfba87375d5a7595e46be8
[2023-07-12 13:59:00] [INFO] == Java Exception: java.lang.AssertionError: com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium: Output contains <!-- /fuzz/DenylistFuzzer-out/crashes/crash-a881bfe8c2a44ddadb2d28de66f3e9da54fe8034
[2023-07-12 13:59:00] [INFO] == Java Exception: java.lang.IndexOutOfBoundsException: start 36, end 1, length 37 /fuzz/DenylistFuzzer-out/crashes/crash-fc4a045cb55197a9c0416e5e526f197d7abf3d01
[2023-07-12 13:59:13] [INFO] #24430	INITED cov: 470 ft: 6943 corp: 2105/9924Kb exec/s: 2714 rss: 973Mb
[2023-07-12 13:59:13] [INFO] [RESULTS] Fuzzing corpus is saved in /fuzz/DenylistFuzzer-out/corpus
[2023-07-12 13:59:13] [INFO] [RESULTS] oom/leak/timeout/crash: 0/0/0/544
[2023-07-12 13:59:13] [INFO] [RESULTS] Fuzzing results are saved in /fuzz/DenylistFuzzer-out/crashes

Мы получили набор из 544 аварийных завершений. Здесь точно понадобится casr.

Однако сначала давайте минимизируем входной корпус:

# sydr-fuzz -c DenyFuzzer.toml cmin
[2023-07-12 14:56:44] [INFO] Original fuzzing corpus saved as /fuzz/DenylistFuzzer-out/corpus-old
[2023-07-12 14:56:44] [INFO] Minimizing corpus /fuzz/DenylistFuzzer-out/corpus
[2023-07-12 14:56:45] [INFO] Jazzer environment: ASAN_OPTIONS=abort_on_error=0,malloc_context_size=0,allocator_may_return_null=1
[2023-07-12 14:56:45] [INFO] Launching Jazzer: cd "/fuzz/DenylistFuzzer-out/jazzer" && ASAN_OPTIONS="abort_on_error=1,malloc_context_size=0,allocator_may_return_null=1" "/usr/local/bin/jazzer" "-merge=1" "-rss_limit_mb=8192" "-detect_leaks=0" "-artifact_prefix=/fuzz/DenylistFuzzer-out/crashes/" "-use_value_profile=1" "-verbosity=2" "--reproducer_path=/dev/null" "--jvm_args=-Xmx2048m:-Xss1024k" "--target_class=DenylistFuzzer" "--agent_path=/usr/local/lib/jazzer_standalone_deploy.jar" "--cp=/out/:/out/gson-2.8.6.jar:/out/json-sanitizer.jar" "/fuzz/DenylistFuzzer-out/corpus" "/fuzz/DenylistFuzzer-out/corpus-old"
[2023-07-12 14:56:46] [INFO] MERGE-OUTER: 24439 files, 0 in the initial corpus, 0 processed earlier
[2023-07-12 14:56:46] [INFO] MERGE-OUTER: attempt 1
[2023-07-12 14:56:57] [INFO] MERGE-OUTER: successful in 1 attempt(s)
[2023-07-12 14:56:57] [INFO] MERGE-OUTER: the control file has 2783113 bytes
[2023-07-12 14:56:57] [INFO] MERGE-OUTER: consumed 1Mb (782Mb rss) to parse the control file
[2023-07-12 14:56:57] [INFO] MERGE-OUTER: 2116 new files with 6973 new features added; 500 new coverage edges

Набор данных в копусе значительно сократился: из 24439 файлов осталось лишь 2116. Давайте посмотрим на покрытие.

Покрытие

Для сбора покрытия будем использовать библиотеку jacoco. Все инструкции по её установке могут быть найдены тут. Она, конечно же, уже присутствует в нашем докер контейнере. Сейчас мы попробуем получить покрытие в виде html отчета. Тут стоит отметить следующee: для того, чтобы в нашем отчете были видны строки исходного кода, необходимо в переменной окружения CASR_SOURCE_DIRS указать пути до директорий с исходным кодом. Давайте сделаем это:

# export CASR_SOURCE_DIRS=/json-sanitizer/src/main/java
# sydr-fuzz -c DenylistFuzzer.toml jacov html
[2023-07-12 15:20:05] [INFO] Running jacov html "/fuzz/DenylistFuzzer.toml"
[2023-07-12 15:20:05] [INFO] Collecting coverage data for each file in corpus: /fuzz/DenylistFuzzer-out/corpus
[2023-07-12 15:20:05] [INFO] Jazzer environment: ASAN_OPTIONS=allocator_may_return_null=1,malloc_context_size=0,abort_on_error=1
[2023-07-12 15:20:05] [INFO] Launching Jazzer: cd "/fuzz/DenylistFuzzer-out/jazzer" && ASAN_OPTIONS="allocator_may_return_null=1,malloc_context_size=0,abort_on_error=1" "/usr/local/bin/jazzer" "-detect_leaks=0" "-artifact_prefix=/fuzz/DenylistFuzzer-out/crashes/" "-use_value_profile=1" "-verbosity=2" "--reproducer_path=/dev/null" "--jvm_args=-Xmx2048m:-Xss1024k" "--target_class=DenylistFuzzer" "--agent_path=/usr/local/lib/jazzer_standalone_deploy.jar" "--cp=/out/:/out/gson-2.8.6.jar:/out/json-sanitizer.jar" "-rss_limit_mb=4096" "--additional_jvm_args=-javaagent\\:/usr/local/lib/jacoco/lib/jacocoagent.jar=destfile=/fuzz/DenylistFuzzer-out/coverage/jacoco.exec" "--nohooks" "-runs=0" "/fuzz/DenylistFuzzer-out/corpus"
[2023-07-12 15:20:06] [INFO] Running java to collect coverage html: "/usr/bin/java" "-jar" "/usr/local/lib/jacoco/lib/jacococli.jar" "report" "/fuzz/DenylistFuzzer-out/coverage/jacoco.exec" "--sourcefiles" "/json-sanitizer/src/main/java" "--classfiles" "/out/" "--classfiles" "/out/gson-2.8.6.jar" "--classfiles" "/out/json-sanitizer.jar" "--html" "/fuzz/DenylistFuzzer-out/coverage/html"
[INFO] Loading execution data file /fuzz/DenylistFuzzer-out/coverage/jacoco.exec.
[INFO] Analyzing 170 classes.
[2023-07-12 15:20:07] [INFO] html coverage report is saved to "/fuzz/DenylistFuzzer-out/coverage/html"

Покрытие в html отчете будет выглядеть так:

guide_cov

Сортировка аварийных завершений

Пришло время для анализа аварийных завершений с помощью casr. Давайте его запустим:

# sydr-fuzz -c DenylistFuzzer.toml casr

Вы можете узнать больше о casr из репозитория casr или из другого гайда.

Вывод команды sydr-fuzz casr выглядит следующим образом:

[2023-07-12 16:32:54] [INFO] Casr-cluster: deduplication of casr reports...
[2023-07-12 16:32:55] [INFO] Reports before deduplication: 544; after: 9
[2023-07-12 16:32:55] [INFO] Casr-cluster: clustering casr reports...
[2023-07-12 16:32:55] [INFO] Reports before clustering: 9. Clusters: 2
[2023-07-12 16:32:55] [INFO] Copying inputs...
[2023-07-12 16:32:55] [INFO] Done!
[2023-07-12 16:32:55] [INFO] ==> <cl1>
[2023-07-12 16:32:55] [INFO] Crash: /fuzz/DenylistFuzzer-out/casr/cl1/crash-7fc508793e29a20a61c0a590e3c39f4e5c64b194
[2023-07-12 16:32:55] [INFO]   casr-java: NOT_EXPLOITABLE: com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: DenylistFuzzer.java:36
[2023-07-12 16:32:55] [INFO]   Similar crashes: 1
[2023-07-12 16:32:55] [INFO] Crash: /fuzz/DenylistFuzzer-out/casr/cl1/crash-25535dd57fa4a95833f18b1856415477c1f9cd88
[2023-07-12 16:32:55] [INFO]   casr-java: NOT_EXPLOITABLE: com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium: DenylistFuzzer.java:44
[2023-07-12 16:32:55] [INFO]   Similar crashes: 1
[2023-07-12 16:32:55] [INFO] Crash: /fuzz/DenylistFuzzer-out/casr/cl1/crash-01322fa015e11aa60b7131cc50ef85950b33b0cf
[2023-07-12 16:32:55] [INFO]   casr-java: NOT_EXPLOITABLE: com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: DenylistFuzzer.java:38
[2023-07-12 16:32:55] [INFO]   Similar crashes: 1
[2023-07-12 16:32:55] [INFO] Crash: /fuzz/DenylistFuzzer-out/casr/cl1/crash-03d226b16fd27adf10e4d521afe0cd82dcfbcf2c
[2023-07-12 16:32:55] [INFO]   casr-java: NOT_EXPLOITABLE: com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium: DenylistFuzzer.java:46
[2023-07-12 16:32:55] [INFO]   Similar crashes: 1
[2023-07-12 16:32:55] [INFO] Cluster summary -> com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: 2 com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium: 2
[2023-07-12 16:32:55] [INFO] ==> <cl2>
[2023-07-12 16:32:55] [INFO] Crash: /fuzz/DenylistFuzzer-out/casr/cl2/crash-f5d7fc872cabb309c3c7f97cd5a44c698e3beb25
[2023-07-12 16:32:55] [INFO]   casr-java: NOT_EXPLOITABLE: java.lang.IndexOutOfBoundsException: JsonSanitizer.java:717
[2023-07-12 16:32:55] [INFO]   Similar crashes: 5
[2023-07-12 16:32:55] [INFO] Cluster summary -> java.lang.IndexOutOfBoundsException: 5
[2023-07-12 16:32:55] [INFO] SUMMARY -> java.lang.IndexOutOfBoundsException: 5 com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium: 2 com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh: 2
[2023-07-12 16:32:55] [INFO] Crashes and Casr reports are saved in /fuzz/DenylistFuzzer-out/casr

Перед дедупликации у нас был набор из 544 аварийных завершений. После дедупликации осталось обработать всего лишь 9 отчетов, которые кластеризация разделила на 2 кластера. Давайте посмотрим на отчет из второго кластера:

guide_cli

Произошел выход за границу массива при добавлении части строки. Похоже на потенциальную ошибку.

Заключение

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