| name | implementing-fuzz-testing-in-cicd-with-aflplusplus |
| description | 将 AFL++ 覆盖率引导的模糊测试集成到 CI/CD 管道中,以发现 C/C++ 和编译型应用中的内存损坏、输入处理和逻辑漏洞。 |
| domain | cybersecurity |
| subdomain | devsecops |
| tags | ["aflplusplus","fuzz-testing","cicd","coverage-guided-fuzzing","security-testing","vulnerability-discovery","afl"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
在 CI/CD 中使用 AFL++ 实施模糊测试
概述
AFL++(American Fuzzy Lop Plus Plus)是 AFL 的社区维护分支,为发现编译型应用中的漏洞提供最先进的覆盖率引导模糊测试。AFL++ 使用遗传算法变异输入,跟踪代码覆盖率以寻找触发崩溃、挂起和未定义行为的新执行路径。在 CI/CD 环境中,AFL++ 可以集成到持续测试解析器、协议处理程序、文件格式处理器以及任何处理不受信任输入的代码中。AFL++ 支持持久模式以实现高速模糊测试(每秒超过 100,000 次执行)、自定义变异器、用于纯二进制模糊测试的 QEMU 模式以及用于自动字典提取的 CmpLog/RedQueen。
前置条件
- 基于 Linux 的 CI Runner(AFL++ 原生不支持 Windows)
- GCC 或 Clang 编译器工具链
- 已安装 AFL++(
apt install aflplusplus 或从源码构建)
- 带有隔离输入处理的测试线束函数的目标应用
- 有效输入样本的种子语料库
核心概念
覆盖率引导模糊测试
AFL++ 在编译时(或通过 QEMU/Frida 对纯二进制目标)对目标二进制文件进行插桩,以跟踪每个输入执行了哪些代码路径。当变异输入触发新的代码路径时,它会被保存到语料库中以供进一步变异。这种反馈循环使 AFL++ 能够系统地探索程序状态空间。
插桩模式
| 模式 | 使用场景 | 性能 |
|---|
afl-clang-fast(LTO) | 有源码,最佳性能 | 最高 |
afl-clang-fast | 有源码,标准 | 高 |
afl-gcc-fast | GCC 项目 | 高 |
QEMU 模式 | 纯二进制,无源码 | 中 |
Frida 模式 | 纯二进制,跨平台 | 中 |
Unicorn 模式 | 固件,嵌入式 | 低 |
持久模式
持久模式通过在循环中进行模糊测试来避免 fork 开销:
#include <unistd.h>
__AFL_FUZZ_INIT();
int main() {
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
parse_input(buf, len);
}
return 0;
}
实施步骤
步骤 1 — 构建模糊测试线束
创建一个将 AFL++ 输入传递给目标函数的测试线束:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "target_parser.h"
__AFL_FUZZ_INIT();
int main() {
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
if (len < 4) continue;
parser_context_t ctx;
parser_init(&ctx);
parser_process(&ctx, buf, len);
parser_cleanup(&ctx);
}
return 0;
}
步骤 2 — 使用 AFL++ 插桩编译
export CC=afl-clang-fast
export CXX=afl-clang-fast++
export AFL_USE_ASAN=1
$CC -o fuzz_harness fuzz_harness.c -ltarget_parser -fsanitize=address
$CC -o fuzz_harness_cmplog fuzz_harness.c -ltarget_parser \
-fsanitize=address -DCMPLOG
步骤 3 — 准备种子语料库
mkdir -p corpus/
cp test_inputs/* corpus/
afl-cmin -i corpus/ -o corpus_min/ -- ./fuzz_harness @@
mkdir -p corpus_tmin/
for f in corpus_min/*; do
afl-tmin -i "$f" -o "corpus_tmin/$(basename $f)" -- ./fuzz_harness @@
done
步骤 4 — 配置 CI/CD 集成
GitHub Actions:
name: Fuzz Testing
on:
push:
branches: [main]
schedule:
- cron: '0 2 * * *'
jobs:
fuzz:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Install AFL++
run: |
sudo apt-get update
sudo apt-get install -y aflplusplus
- name: Restore corpus cache
uses: actions/cache@v4
with:
path: corpus/
key: fuzz-corpus-${{ github.sha }}
restore-keys: fuzz-corpus-
- name: Build fuzzing harness
run: |
export CC=afl-clang-fast
export AFL_USE_ASAN=1
make fuzz_harness
- name: Run
步骤 5 — 夜间运行的并行模糊测试
for i in $(seq 1 $(nproc)); do
afl-fuzz -S fuzzer_$i \
-i corpus/ \
-o findings/ \
-- ./fuzz_harness @@ &
done
wait
afl-cmin -i findings/*/queue/ -o corpus_merged/ -- ./fuzz_harness @@
步骤 6 — 崩溃分析
for crash in findings/*/crashes/*; do
echo "=== Testing: $crash ==="
timeout 5 ./fuzz_harness_asan "$crash" 2>&1 | head -20
echo "---"
done
afl-collect findings/ crashes_deduped/ -- ./fuzz_harness @@
AFL++ CI/CD 最佳实践
| 设置 | CI 短时运行 | 夜间长时运行 |
|---|
| 时长 | 30-60 分钟 | 4-24 小时 |
| 模式 | -S(仅次级) | -S(CI 不用 -M) |
AFL_CMPLOG_ONLY_NEW | 1 | 1 |
AFL_FAST_CAL | 1 | 0 |
AFL_NO_STARTUP_CALIBRATION | 1 | 0 |
| 语料库缓存 | 必需 | 必需 |
| 并行实例 | 1-2 | nproc |
监控模糊测试活动
afl-whatsup findings/
参考资料