覆盖率导向的模糊测试
- Tier: 旗舰版
- Offering: JihuLab.com, 私有化部署
极狐GitLab 的覆盖引导模糊测试会向您的应用程序的仪器版本发送随机输入,以引发意外行为。这样的行为表明您应该解决的错误。极狐GitLab 允许您将覆盖引导模糊测试添加到您的流水线中。这有助于您发现其他 QA 流程可能遗漏的错误和潜在的安全问题。
您应该在 极狐GitLab 安全 中的其他安全扫描仪和您自己的测试流程中使用模糊测试。如果您使用 极狐GitLab CI/CD,可以在您的 CI/CD 工作流中运行覆盖引导模糊测试。
概述,请参见 Coverage Fuzzing。
覆盖引导模糊测试过程
模糊测试过程:
- 编译目标应用程序。
- 使用 gitlab-cov-fuzz 工具运行仪器应用程序。
- 解析并分析模糊器输出的异常信息。
- 从以下位置下载 语料库:
- 以前的流水线。
- 如果 COVFUZZ_USE_REGISTRY 设置为 true,则从 语料库注册表。
- 从以前的流水线中下载崩溃事件。
- 将解析的崩溃事件和数据输出到 gl-coverage-fuzzing-report.json 文件。
- 更新语料库,以下是两种方式:
- 在作业的流水线中。
- 如果 COVFUZZ_USE_REGISTRY 设置为 true,则在语料库注册表中。
覆盖引导模糊测试的结果在 CI/CD 流水线中可用。
支持的模糊引擎和语言
您可以使用以下模糊引擎测试指定的语言。
语言 | 模糊引擎 | 示例 |
---|---|---|
C/C++ | libFuzzer | c-cpp-example |
Go | go-fuzz (libFuzzer support) | go-fuzzing-example |
Swift | libFuzzer | swift-fuzzing-example |
Rust | cargo-fuzz (libFuzzer support) | rust-fuzzing-example |
Java (Maven only)1 | Javafuzz (recommended) | javafuzz-fuzzing-example |
Java | JQF (not preferred) | jqf-fuzzing-example |
JavaScript | jsfuzz | jsfuzz-fuzzing-example |
Python | pythonfuzz | pythonfuzz-fuzzing-example |
AFL (任何在 AFL 顶层工作的语言) | AFL | afl-fuzzing-example |
确认覆盖引导模糊测试的状态
确认覆盖引导模糊测试的状态:
- 在左侧边栏,选择 搜索或转到 并找到您的项目。
- 选择 安全 > 安全配置。
- 在 覆盖模糊测试 部分中,状态为:
- 未配置
- 已启用
- 提示升级到极狐GitLab 旗舰版。
启用覆盖引导模糊测试
要启用覆盖引导模糊测试,请编辑 .gitlab-ci.yml:
-
将 fuzz 阶段添加到阶段列表中。
-
如果您的应用程序不是用 Go 编写的,请使用匹配的模糊引擎 提供 Docker 镜像。例如:
yamlimage: python:latest
-
包含作为极狐GitLab 安装的一部分提供的Coverage-Fuzzing.gitlab-ci.yml 模板。
-
根据您的需求自定义 my_fuzz_target 作业。
覆盖引导模糊配置示例摘录
yaml1stages: 2 - fuzz 3 4include: 5 - template: Coverage-Fuzzing.gitlab-ci.yml 6 7my_fuzz_target: 8 extends: .fuzz_base 9 script: 10 # 在这些步骤中构建您的模糊目标二进制文件,然后使用 gitlab-cov-fuzz 运行它 11 # 请参阅我们的示例仓库,了解如何使用我们支持的任何语言做到这一点 12 - ./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 流水线中配置覆盖引导模糊测试。
所有极狐GitLab 安全扫描工具的自定义都应在合并请求中进行测试,然后再将这些更改合并到默认分支。未能做到这一点可能会产生意想不到的结果,包括大量误报。
CI/CD 变量 | 描述 |
---|---|
COVFUZZ_ADDITIONAL_ARGS | 传递给 gitlab-cov-fuzz 的参数。用于自定义底层模糊引擎的行为。阅读模糊引擎的文档以获取完整的参数列表。 |
COVFUZZ_BRANCH | 运行长时间运行的模糊作业的分支。在所有其他分支上,只运行模糊回归测试。默认值:仓库的默认分支。 |
COVFUZZ_SEED_CORPUS | 种子语料库目录的路径。默认值:空。 |
COVFUZZ_URL_PREFIX | 为离线环境使用克隆的 gitlab-cov-fuzz 仓库的路径。您应该只在使用离线环境时更改此值。默认值:https://jihulab.com/gitlab-cn/security-products/analyzers/gitlab-cov-fuzz/-/raw。 |
COVFUZZ_USE_REGISTRY | 设置为 true 以将语料库存储在极狐GitLab 语料库注册表中。如果此变量设置为 true,则需要 COVFUZZ_CORPUS_NAME 和 COVFUZZ_GITLAB_TOKEN 变量。默认值:false。 |
COVFUZZ_CORPUS_NAME | 作业中使用的语料库名称。 |
COVFUZZ_GITLAB_TOKEN | 配置为 个人访问令牌 或 项目访问令牌 的环境变量,具有 API 读/写访问权限。 |
种子语料库
种子语料库中的文件必须手动更新。它们不会由覆盖引导模糊测试作业更新或覆盖。
输出
每个模糊步骤输出这些产物:
- gl-coverage-fuzzing-report.json:包含覆盖引导模糊测试及其结果详细信息的报告。
- artifacts.zip:此文件包含两个目录:
- corpus:包含当前和以前所有作业生成的所有测试用例。
- crashes:包含当前作业发现的所有崩溃事件以及以前作业中未修复的崩溃事件。
您可以从 CI/CD 流水线页面下载 JSON 报告文件。有关更多信息,请参见 下载产物。
语料库注册表
语料库注册表是语料库的库。项目注册表中的语料库对该项目中的所有作业可用。项目范围的注册表比每个作业一个语料库的默认选项更有效地管理语料库。
语料库注册表使用软件包注册表存储项目的语料库。为了确保数据完整性,存储在注册表中的语料库是隐藏的。
当您下载语料库时,文件名为 artifacts.zip,无论最初上传语料库时使用的文件名是什么。此文件仅包含语料库,这与您可以从 CI/CD 流水线下载的产物文件不同。此外,具有 Reporter 或以上权限的项目成员可以使用直接下载链接下载语料库。
查看语料库注册表的详细信息
查看语料库注册表的详细信息:
- 在左侧边栏,选择 搜索或转到 并找到您的项目。
- 选择 安全 > 安全配置。
- 在 覆盖模糊测试 部分,选择 管理语料库。
在语料库注册表中创建语料库
要在语料库注册表中创建语料库,可以:
- 在流水线中创建语料库
- 上传现有的语料库文件
在流水线中创建语料库
在流水线中创建语料库:
- 在 .gitlab-ci.yml 文件中,编辑 my_fuzz_target 作业。
- 设置以下变量:
- 将 COVFUZZ_USE_REGISTRY 设置为 true。
- 设置 COVFUZZ_CORPUS_NAME 以命名语料库。
- 设置 COVFUZZ_GITLAB_TOKEN 为个人访问令牌的值。
在 my_fuzz_target 作业运行后,语料库会存储在语料库注册表中,名称由 COVFUZZ_CORPUS_NAME 变量提供。语料库在每次流水线运行时更新。
上传语料库文件
上传现有的语料库文件:
- 在左侧边栏,选择 搜索或转到 并找到您的项目。
- 选择 安全 > 安全配置。
- 在 覆盖模糊测试 部分,选择 管理语料库。
- 选择 新建语料库。
- 完成字段。
- 选择 上传文件。
- 选择 添加。
现在,您可以在 .gitlab-ci.yml 文件中引用语料库。确保 COVFUZZ_CORPUS_NAME 变量中使用的值与上传语料库文件的名称完全匹配。
使用存储在语料库注册表中的语料库
要使用存储在语料库注册表中的语料库,您必须根据其名称引用它。要确认相关语料库的名称,请查看语料库注册表的详细信息。
先决条件:
- 在 .gitlab-ci.yml 文件中设置以下变量:
- 将 COVFUZZ_USE_REGISTRY 设置为 true。
- 将 COVFUZZ_CORPUS_NAME 设置为语料库的名称。
- 将 COVFUZZ_GITLAB_TOKEN 设置为个人访问令牌的值。
覆盖引导模糊测试报告
覆盖引导模糊测试报告示例:
json1{ 2 "version": "v1.0.8", 3 "regression": false, 4 "exit_code": -1, 5 "vulnerabilities": [ 6 { 7 "category": "coverage_fuzzing", 8 "message": "Heap-buffer-overflow\nREAD 1", 9 "description": "Heap-buffer-overflow\nREAD 1", 10 "severity": "Critical", 11 "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", 12 "scanner": { 13 "id": "libFuzzer", 14 "name": "libFuzzer" 15 }, 16 "location": { 17 "crash_address": "0x602000001573", 18 "crash_state": "FuzzMe\nstart\nstart+0x0\n\n", 19 "crash_type": "Heap-buffer-overflow\nREAD 1" 20 }, 21 "tool": "libFuzzer" 22 } 23 ] 24}
覆盖引导模糊测试的持续时间
覆盖引导模糊测试的可用持续时间为:
- 10 分钟持续时间(默认值):推荐用于默认分支。
- 60 分钟持续时间:推荐用于开发分支和合并请求。较长的持续时间提供更大的覆盖范围。 在 COVFUZZ_ADDITIONAL_ARGS 变量中设置值 --regression=true。
连续覆盖引导模糊测试
还可以运行覆盖引导模糊作业更长时间,而不会阻塞您的主流水线。此配置使用极狐GitLab 父子流水线。
在这种情况下建议的工作流程是在主或开发分支上进行长时间运行的异步模糊作业,而在所有其他分支和合并请求上进行短时间的同步模糊作业。这种方式平衡了快速完成每次提交流水线的需要,同时也为模糊器提供了大量时间来全面探索和测试应用程序。长时间运行的模糊作业通常对于覆盖引导模糊器发现代码库中的深层错误是必要的。
以下是该工作流程的 .gitlab-ci.yml 文件摘录。
yaml1 2sync_fuzzing: 3 variables: 4 COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=300' 5 trigger: 6 include: .covfuzz-ci.yml 7 strategy: depend 8 rules: 9 - if: $CI_COMMIT_BRANCH != 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event' 10 11async_fuzzing: 12 variables: 13 COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=3600' 14 trigger: 15 include: .covfuzz-ci.yml 16 rules: 17 - if: $CI_COMMIT_BRANCH == 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'
这将创建两个作业:
-
sync_fuzzing:在阻塞配置中短时间运行所有模糊目标。这可以发现简单错误,并使您确信您的合并请求不会引入新错误或导致旧错误重新出现。
-
async_fuzzing:在您的分支上运行,并在不阻塞您的开发周期和合并请求的情况下发现代码中的深层错误。
FIPS 启用的二进制文件
从极狐GitLab 15.0 开始,覆盖模糊二进制文件在 Linux x86 上使用 golang-fips 编译,并使用 OpenSSL 作为加密后端。有关详细信息,请参见极狐GitLab 中的 FIPS 合规性与 Go。
离线环境
要在离线环境中使用覆盖模糊测试:
-
克隆 gitlab-cov-fuzz 到您的离线极狐GitLab 实例可以访问的私有仓库。
-
对每个模糊步骤,设置 COVFUZZ_URL_PREFIX 为 ${NEW_URL_GITLAB_COV_FUZ}/-/raw,其中 NEW_URL_GITLAB_COV_FUZ 是您在第一步中设置的私有 gitlab-cov-fuzz 克隆的 URL。
与漏洞交互
发现漏洞后,您可以解决它。合并请求小部件列出漏洞,并包含下载模糊产物的按钮。通过选择检测到的漏洞之一,您可以查看其详细信息。
您还可以从 安全仪表板查看漏洞,该仪表板显示您群组、项目和流水线中的所有安全漏洞的概览。
选择漏洞会打开一个模态窗口,提供有关漏洞的附加信息:
- 状态:漏洞的状态。与任何类型的漏洞一样,覆盖模糊漏洞可以是检测到、确认、已解雇或已解决。
- 项目:漏洞所在的项目。
- 崩溃类型:代码中的崩溃或弱点类型。这通常映射到 CWE。
- 崩溃状态:堆栈跟踪的标准化版本,包含崩溃的最后三个函数(没有随机地址)。
- 堆栈跟踪片段:堆栈跟踪的最后几行,显示有关崩溃的详细信息。
- 标识符:漏洞的标识符。这映射到 CVE 或 CWE。
- 严重性:漏洞的严重性。这可以是严重、高、中、低、信息或未知。
- 扫描仪:检测漏洞的扫描仪(例如,覆盖模糊测试)。
- 扫描仪提供者:进行扫描的引擎。对于覆盖模糊测试,这可以是支持的模糊引擎和语言中列出的任何引擎。
故障排除
错误 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,请确保允许重复。有关更多详细信息,请参见禁用发布重复的软件包名称。