覆盖率引导的模糊测试

覆盖率引导的模糊测试将随机输入发送到应用程序的检测版本,导致意外行为。这种行为表明您应该解决的一个错误。 极狐GitLab 允许您将覆盖率引导的模糊测试添加到您的流水线中。这有助于您发现其他 QA 流程可能遗漏的错误和潜在安全问题。

除了极狐GitLab 中的其他安全扫描器和您自己的测试流程,我们建议您使用模糊测试。如果您使用极狐GitLab CI/CD,则可以将覆盖率引导的模糊测试作为 CI/CD 工作流程的一部分运行。

覆盖率引导的模糊测试过程

模糊测试过程:

  1. 编译目标应用程序。
  2. 使用 gitlab-cov-fuzz 工具运行检测的应用程序。
  3. 解析分析 fuzzer 输出的异常信息。
  4. 从以下一处下载语料库
    • 以前的流水线。
    • COVFUZZ_USE_REGISTRY 设置为 true,从语料库 registy。
  5. 从之前的流水线下载崩溃事件。
  6. 将解析后的崩溃事件和数据输出到 gl-coverage-fuzzing-report.json 文件。
  7. 从以下一处更新语料库:
    • 作业的流水线中。
    • COVFUZZ_USE_REGISTRY 设置为 true,从语料库 registy。

覆盖率引导的模糊测试的结果可在 CI/CD 流水线中获得。

支持的模糊测试引擎和语言

您可以使用以下模糊引擎来测试指定的语言。

语言 模糊测试引擎
C/C++ libFuzzer
GoLang go-fuzz (libFuzzer support)
Swift libFuzzer
Rust cargo-fuzz (libFuzzer support)
Java Javafuzz(推荐)
Java JQF(非首选)
JavaScript jsfuzz
Python pythonfuzz
AFL(任何在 AFL 之上工作的语言) AFL

确认覆盖率引导的模糊测试的状态

要确认覆盖率引导的模糊测试的状态:

  1. 在顶部栏上,选择 主菜单 > 项目 并找到您的项目。
  2. 在左侧边栏上,选择 安全与合规 > 配置
  3. 覆盖率测试 部分,状态为:
    • Not configured
    • Enabled
    • 升级到旗舰版的提示

启用覆盖率引导的模糊测试

要启用覆盖率引导的模糊测试,请编辑 .gitlab-ci.yml

  1. fuzz 阶段添加到阶段列表中。

  2. 如果您的应用程序不是用 Go 编写的,请使用匹配的模糊测试引擎提供 Docker 镜像。例如:

    image: python:latest
    
  3. 包括作为安装实例一部分的 Coverage-Fuzzing.gitlab-ci.yml 模板

  4. 自定义 my_fuzz_target 作业以满足您的要求。

覆盖率引导的模糊测试配置示例摘录

stages:
  - fuzz

include:
  - template: Coverage-Fuzzing.gitlab-ci.yml

my_fuzz_target:
  extends: .fuzz_base
  script:
    # Build your fuzz target binary in these steps, then run it with gitlab-cov-fuzz
    # See our example repos for how you could do this with any of our supported languages
    - ./gitlab-cov-fuzz run --regression=$REGRESSION -- <your fuzz target>

Coverage-Fuzzing 模板包括隐藏作业 .fuzz_base,您必须扩展它,用于每个模糊测试目标。每个模糊测试目标必须有一个单独的作业。例如,go-fuzzing-example 项目包含一项为其单个模糊测试目标扩展 .fuzz_base 的作业。

请注意,隐藏作业 .fuzz_base 使用多个 YAML 键,您不得在自己的作业中覆盖这些键。如果您在自己的作业中包含这些密钥,则必须复制它们的原始内容:

  • before_script
  • artifacts
  • rules

可用的 CI/CD 变量

使用以下变量在 CI/CD 流水线中配置覆盖率引导的模糊测试。

caution在将这些更改合并到默认分支之前,应在合并请求中测试极狐GitLab 安全扫描工具的所有自定义。不这样做会产生意想不到的结果,包括大量误报。
CI/CD 变量 描述
COVFUZZ_ADDITIONAL_ARGS 传递给 gitlab-cov-fuzz 的参数。用于自定义底层模糊引擎的行为。阅读模糊测试引擎的文档以获取完整的参数列表。
COVFUZZ_BRANCH 要运行长时间运行的模糊测试作业的分支。在所有其他分支上,仅运行模糊回归测试。默认值:仓库的默认分支。
COVFUZZ_SEED_CORPUS Seed 语料库目录的路径。默认值:空。
COVFUZZ_URL_PREFIX 克隆的 gitlab-cov-fuzz 仓库的路径,以供离线环境使用。您应该只在使用离线环境时更改此值。
COVFUZZ_USE_REGISTRY 设置为 true,将语料库存储在极狐GitLab 语料库 registry中。如果此变量设置为 true,则需要变量 COVFUZZ_CORPUS_NAMECOVFUZZ_GITLAB_TOKEN。默认值:false。引入于 14.8 版本。
COVFUZZ_CORPUS_NAME 要在作业中使用的语料库的名称。引入于 14.8 版本。
COVFUZZ_GITLAB_TOKEN 使用个人访问令牌或具有 API 读/写访问权限的项目访问令牌配置的环境变量。引入于 14.8 版本。

Seed 语料库

Seed 语料库中的文件必须手动更新。它们不会被覆盖指南模糊测试作业更新或覆盖。

输出

每个模糊测试步骤都会输出这些产物:

  • gl-coverage-fuzzing-report.json:包含覆盖率引导的模糊测试及其结果的详细信息的报告。
  • artifacts.zip:此文件包含两个目录:
    • corpus:包含当前和所有以前的作业生成的所有测试用例。
    • crashes:包含当前作业找到的所有崩溃事件以及以前作业中未修复的事件。

您可以从 CI/CD 流水线页面下载 JSON 报告文件。有关更多信息,请参阅下载产物

语料库 registry

  • 引入于 14.8 版本。
  • 普遍可用于 14.9 版本。功能标志 corpus_managementcorpus_management_ui 已删除。

项目 registry 中的语料库可用于该项目中的所有作业。与每个作业一个语料库的默认选项相比,项目范围的 registry 是一种更有效的语料库管理方式。

语料库 registry 使用软件包库来存储项目的语料库。存储在 registry 中的语料库被隐藏以确保数据完整性。

在 UI 中,通过语料库管理,您可以:

  • 查看语料库 registry 的详细信息。
  • 下载语料库。
  • 删除语料库。
  • 创建一个新的语料库。

当您下载语料库时,该文件名为 artifacts.zip,与最初上传语料库时使用的文件名无关。此文件仅包含语料库,这与您可以从 CI/CD 流水线下载的产物文件不同。

查看语料库 registry 的详细信息

要查看语料库 registry 的详细信息:

  1. 在顶部栏上,选择 主菜单 > 项目 并找到您的项目。
  2. 在左侧边栏上,选择 安全与合规 > 配置
  3. Coverage Fuzzing 部分,选择 管理语料库

在语料库 registry 中创建语料库

要在语料库 registry 中创建语料库,请执行以下任一操作:

  • 在流水线中创建语料库
  • 上传现有的语料库文件

在流水线中创建语料库

要在流水线中创建语料库:

  1. .gitlab-ci.yml 文件中,编辑 my_fuzz_target 作业。
  2. 设置以下变量:
    • 设置 COVFUZZ_USE_REGISTRYtrue
    • 设置 COVFUZZ_CORPUS_NAME 来命名语料库。
    • COVFUZZ_GITLAB_TOKEN 设置为个人访问令牌的值。

my_fuzz_target 作业运行后,语料库存储在语料库 registry 中,名称由 COVFUZZ_CORPUS_NAME 变量提供。每次运行流水线时都会更新语料库。

上传语料库文件

要上传现有的语料库文件:

  1. 在顶部栏上,选择 主菜单 > 项目 并找到您的项目。
  2. 在左侧边栏上,选择 安全与合规 > 配置
  3. Coverage Fuzzing 部分,选择 管理语料库
  4. 选择 新建语料库
  5. 填写字段。
  6. 选择 上传文件
  7. 选择 添加

您现在可以在 .gitlab-ci.yml 文件中引用语料库。 确保 COVFUZZ_CORPUS_NAME 变量中使用的值与上传的语料库文件的名称完全匹配。

使用存储在语料库 registry 中的语料库

要使用存储在语料库 registry 中的语料库,您必须通过其名称引用它。要确认相关语料的名称,请查看语料库的详细信息。

先决条件:

  1. .gitlab-ci.yml文件中设置以下变量:
    • COVFUZZ_USE_REGISTRY 设置为 true
    • COVFUZZ_CORPUS_NAME 设置为语料库的名称。
    • COVFUZZ_GITLAB_TOKEN 设置为个人访问令牌的值。

覆盖率引导的模糊测试报告

引入于 13.3 版本作为 Alpha 功能。

覆盖率引导的模糊测试报告示例:

{
  "version": "v1.0.8",
  "regression": false,
  "exit_code": -1,
  "vulnerabilities": [
    {
      "category": "coverage_fuzzing",
      "message": "Heap-buffer-overflow\nREAD 1",
      "description": "Heap-buffer-overflow\nREAD 1",
      "severity": "Critical",
      "stacktrace_snippet": "INFO: Seed: 3415817494\nINFO: Loaded 1 modules   (7 inline 8-bit counters): 7 [0x10eee2470, 0x10eee2477), \nINFO: Loaded 1 PC tables (7 PCs): 7 [0x10eee2478,0x10eee24e8), \nINFO:        5 files found in corpus\nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes\nINFO: seed corpus: files: 5 min: 1b max: 4b total: 14b rss: 26Mb\n#6\tINITED cov: 7 ft: 7 corp: 5/14b exec/s: 0 rss: 26Mb\n=================================================================\n==43405==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000001573 at pc 0x00010eea205a bp 0x7ffee0d5e090 sp 0x7ffee0d5e088\nREAD of size 1 at 0x602000001573 thread T0\n    #0 0x10eea2059 in FuzzMe(unsigned char const*, unsigned long) fuzz_me.cc:9\n    #1 0x10eea20ba in LLVMFuzzerTestOneInput fuzz_me.cc:13\n    #2 0x10eebe020 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:556\n    #3 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n    #4 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n    #5 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n    #6 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n    #7 0x10eedaf82 in main FuzzerMain.cpp:19\n    #8 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\n0x602000001573 is located 0 bytes to the right of 3-byte region [0x602000001570,0x602000001573)\nallocated by thread T0 here:\n    #0 0x10ef92cfd in wrap__Znam+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64+0x50cfd)\n    #1 0x10eebdf31 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:541\n    #2 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n    #3 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n    #4 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n    #5 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n    #6 0x10eedaf82 in main FuzzerMain.cpp:19\n    #7 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\nSUMMARY: AddressSanitizer: heap-buffer-overflow fuzz_me.cc:9 in FuzzMe(unsigned char const*, unsigned long)\nShadow bytes around the buggy address:\n  0x1c0400000250: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000260: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000270: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000280: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n  0x1c0400000290: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n=\u003e0x1c04000002a0: fa fa fd fa fa fa fd fa fa fa fd fa fa fa[03]fa\n  0x1c04000002b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x1c04000002f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07 \n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n  Global redzone:          f9\n  Global init order:       f6\n  Poisoned by user:        f7\n  Container overflow:      fc\n  Array cookie:            ac\n  Intra object redzone:    bb\n  ASan internal:           fe\n  Left alloca redzone:     ca\n  Right alloca redzone:    cb\n  Shadow gap:              cc\n==43405==ABORTING\nMS: 1 EraseBytes-; base unit: de3a753d4f1def197604865d76dba888d6aefc71\n0x46,0x55,0x5a,\nFUZ\nartifact_prefix='./crashes/'; Test unit written to ./crashes/crash-0eb8e4ed029b774d80f2b66408203801cb982a60\nBase64: RlVa\nstat::number_of_executed_units: 122\nstat::average_exec_per_sec:     0\nstat::new_units_added:          0\nstat::slowest_unit_time_sec:    0\nstat::peak_rss_mb:              28",
      "scanner": {
        "id": "libFuzzer",
        "name": "libFuzzer"
      },
      "location": {
        "crash_address": "0x602000001573",
        "crash_state": "FuzzMe\nstart\nstart+0x0\n\n",
        "crash_type": "Heap-buffer-overflow\nREAD 1"
      },
      "tool": "libFuzzer"
    }
  ]
}

覆盖率引导的模糊测试的持续时间

覆盖率引导的模糊测试的可用持续时间是:

  • 10 分钟持续时间(默认):推荐用于默认分支。
  • 60 分钟持续时间:推荐用于开发分支和合并请求。更长的持续时间提供更大的覆盖范围。在 COVFUZZ_ADDITIONAL_ARGS 变量中设置值 --regression=true

连续覆盖率引导的模糊测试

还可以在不阻塞主流水线的情况下更长时间地运行覆盖率引导的模糊测试作业。此配置使用极狐GitLab 父子流水线

在这种情况下,建议的工作流程是在主分支或开发分支上进行长时间运行的异步模糊测试作业,并在所有其他分支和 MR 上进行短时间的同步模糊测试作业。这样平衡了快速完成每个提交流水线的需求,同时也给了 fuzzer 大量时间来充分探索和测试应用程序。覆盖率引导的 fuzzer 通常需要长时间运行的 fuzzing 作业,以在您的代码库中找到更深层次的错误。

以下是此工作流程的 .gitlab-ci.yml 文件的摘录。


sync_fuzzing:
  variables:
    COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=300'
  trigger:
    include: .covfuzz-ci.yml
    strategy: depend
  rules:
    - if: $CI_COMMIT_BRANCH != 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'

async_fuzzing:
  variables:
    COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=3600'
  trigger:
    include: .covfuzz-ci.yml
  rules:
    - if: $CI_COMMIT_BRANCH == 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'

创建了两个作业:

  1. sync_fuzzing:在阻塞配置中短时间内运行所有模糊测试目标。这样会发现简单的错误并让您确信您的 MR 不会引入新错误或导致旧错误重新出现。
  2. async_fuzzing:在您的分支上运行并在您的代码中发现深层错误,而不会阻塞您的开发周期和 MR。

启用 FIPS 的二进制文件

从 15.0 版本开始,覆盖率模糊测试二进制文件在 Linux x86 上使用 golang-fips 编译,并使用 OpenSSL 作为加密后端。

离线环境

要在离线环境中使用覆盖模糊测试:

  1. gitlab-cov-fuzz 克隆到您的离线极狐GitLab 实例可以访问的私有仓库。

  2. 对于每个模糊测试步骤,将 COVFUZZ_URL_PREFIX 设置为 ${NEW_URL_GITLAB_COV_FUZ}/-/raw,其中 NEW_URL_GITLAB_COV_FUZ 是您在第一步中设置的私有 gitlab-cov-fuzz 克隆的 URL。

与漏洞交互

发现漏洞后,您可以解决它。 合并请求部件列出了漏洞,并包含一个用于下载模糊测试产物的按钮。通过单击检测到的漏洞之一,您可以查看其详细信息。

Coverage Fuzzing Security Report

您还可以从安全看板查看漏洞,其中显示了您的群组、项目和流水线中的所有安全漏洞的概述。

单击该漏洞会打开一个提供有关该漏洞的其他信息的窗口:

  • Status:漏洞的状态。与任何类型的漏洞一样,可以检测、确认、忽略或解决覆盖模糊漏洞。
  • Project:存在漏洞的项目。
  • Crash type:代码中崩溃或弱点的类型。这通常映射到 CWE
  • Crash state:堆栈跟踪的规范化版本,包含崩溃的最后三个函数(没有随机地址)。
  • Stack trace snippet:堆栈跟踪的最后几行,显示有关崩溃的详细信息。
  • Identifier:漏洞的标识符。这映射到 CVECWE
  • Severity:漏洞的严重性,可以是 Critical、High、Medium、Low、Info 或 Unknown。
  • Scanner:检测到漏洞的扫描仪(例如,Coverage Fuzzing)。
  • Scanner Provider:进行扫描的引擎。对于 Coverage Fuzzing,可以是支持的 fuzzing 引擎和语言中列出的任何引擎。

故障排除

错误 Unable to extract corpus folder from artifacts zip file

如果您看到此错误消息,并且 COVFUZZ_USE_REGISTRY 设置为 true,请确保上传的语料库文件提取到名为 corpus 的文件夹中。

错误 400 Bad request - Duplicate package is not allowed

如果您在运行 COVFUZZ_USE_REGISTRY 设置为 true 的模糊测试作业时看到此错误消息,请确保允许重复。有关更多详细信息,请参阅 重复通用包