implementing-fuzz-testing-in-cicd-with-aflplusplus について
コンパイル済みアプリケーションのCI/CDに、AFL++のカバレッジガイド型ファジングを導入するガイドです。ハーネス、計装、コーパス準備、並列実行、クラッシュ分析を扱います。
主な機能
- 01カバレッジガイド型の入力変異テスト
- 02ソースとバイナリの計装モード
- 03ファジングを高速化する永続モード
- 04シードコーパスの最小化
- 05CI/CDと夜間テストの設定
- 06クラッシュの再現とトリアージ
活用シーン
- 01信頼できない入力を扱うパーサーのテスト
- 02CI/CDでの定期的なファジング実行
- 03ソースコードのないバイナリのファジング
- 04発見したクラッシュの再現と整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill implementing-fuzz-testing-in-cicd-with-aflplusplusインストール後、Claude Code で/implementing-fuzz-testing-in-cicd-with-aflplusplusを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEassets/template.mdreferences/api-reference.mdstandards.mdworkflows.mdscripts/agent.pyprocess.py詳細
AFL++ を使用した CI/CD でのファジングテストの実装
概要
AFL++(American Fuzzy Lop Plus Plus)は、コミュニティによって維持されている AFL のフォークであり、コンパイル済みアプリケーションの脆弱性を発見するための最先端のカバレッジガイド型ファジングテストを提供します。AFL++ は遺伝的アルゴリズムを使用して入力を変異させ、コードカバレッジを追跡することで、クラッシュ、ハング、未定義動作を引き起こす新しい実行パスを見つけ出します。CI/CD 環境では、AFL++ を統合して、パーサー、プロトコルハンドラー、ファイル形式プロセッサー、および信頼できない入力を処理するあらゆるコードを継続的にテストできます。AFL++ は、高速ファジング用の永続モード(毎秒 100,000 回以上の実行)、カスタムミューテーター、バイナリのみのファジング用の QEMU モード、自動辞書抽出用の CmpLog/RedQueen をサポートしています。
使用するタイミング
- 環境で AFL++ を用いた CI/CD でのファジングテストの実装機能を展開または構成する場合
- コンプライアンス要件に沿ったセキュリティ統制を確立する場合
- この分野のセキュリティアーキテクチャを構築または改善する場合
- この実装を必要とするセキュリティ評価を実施する場合
前提条件
- Linux ベースの CI ランナー(AFL++ は Windows をネイティブにサポートしていません)
- GCC または Clang コンパイラツールチェーン
- AFL++ のインストール(
apt install aflplusplusまたはソースからのビルド) - 入力処理を分離するハーネス関数を備えた対象アプリケーション
- 有効な入力サンプルのシードコーパス
中核的な概念
カバレッジガイド型ファジング
AFL++ は、コンパイル時(またはバイナリのみの対象の場合は QEMU/Frida 経由)に対象バイナリをインストルメント化し、各入力がどのコードパスを実行するかを追跡します。変異させた入力が新しいコードパスをトリガーすると、さらに変異させるためにコーパスに保存されます。このフィードバックループにより、AFL++ はプログラムの状態空間を体系的に探索できます。
インストルメンテーションモード
| モード | 用途 | パフォーマンス |
|---|---|---|
afl-clang-fast (LTO) | ソースあり、最高のパフォーマンス | 最高 |
afl-clang-fast | ソースあり、標準 | 高 |
afl-gcc-fast | GCC ベースのプロジェクト | 高 |
QEMU mode | バイナリのみ、ソースなし | 中 |
Frida mode | バイナリのみ、クロスプラットフォーム | 中 |
Unicorn mode | ファームウェア、組み込み | 低 |
永続モード
永続モードは、ループ内でファジングを行うことにより 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;
// Process buf[0..len-1]
parse_input(buf, len);
}
return 0;
}
ワークフロー
ステップ 1 --- ファジングハーネスの構築
AFL++ の入力を対象関数に供給するハーネスを作成します。
// fuzz_harness.c
#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;
// Reset state between iterations
parser_context_t ctx;
parser_init(&ctx);
parser_process(&ctx, buf, len);
parser_cleanup(&ctx);
}
return 0;
}
ステップ 2 --- AFL++ インストルメンテーションでのコンパイル
# Standard instrumentation
export CC=afl-clang-fast
export CXX=afl-clang-fast++
# Enable AddressSanitizer for better crash detection
export AFL_USE_ASAN=1
# Build the target with instrumentation
$CC -o fuzz_harness fuzz_harness.c -ltarget_parser -fsanitize=address
# Build a CmpLog binary for better coverage
$CC -o fuzz_harness_cmplog fuzz_harness.c -ltarget_parser \
-fsanitize=address -DCMPLOG
ステップ 3 --- シードコーパスの準備
mkdir -p corpus/
# Add valid input samples
cp test_inputs/* corpus/
# Minimize the corpus
afl-cmin -i corpus/ -o corpus_min/ -- ./fuzz_harness @@
# Further minimize individual inputs
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 * * *' # Nightly fuzzing
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 AFL++ fuzzing (CI mode)
env:
AFL_CMPLOG_ONLY_NEW: 1
AFL_FAST_CAL: 1
AFL_NO_STARTUP_CALIBRATION: 1
run: |
mkdir -p findings/
timeout 7200 afl-fuzz \
-S ci_fuzzer \
-i corpus/ \
-o findings/ \
-t 5000 \
-- ./fuzz_harness @@ || true
- name: Check for crashes
run: |
CRASHES=$(find findings/ -path "*/crashes/*" -not -name "README.txt" | wc -l)
echo "Found $CRASHES unique crashes"
if [ "$CRASHES" -gt 0 ]; then
echo "::error::AFL++ found $CRASHES crashes"
for crash in findings/*/crashes/*; do
[ -f "$crash" ] && echo "Crash: $crash ($(wc -c < $crash) bytes)"
done
exit 1
fi
- name: Update corpus cache
if: always()
run: |
afl-cmin -i findings/ci_fuzzer/queue/ -o corpus/ -- ./fuzz_harness @@
ステップ 5 --- 夜間実行のための並列ファジング
# Launch multiple secondary instances for better coverage
for i in $(seq 1 $(nproc)); do
afl-fuzz -S fuzzer_$i \
-i corpus/ \
-o findings/ \
-- ./fuzz_harness @@ &
done
# Wait for all fuzzers
wait
# Merge and minimize corpus
afl-cmin -i findings/*/queue/ -o corpus_merged/ -- ./fuzz_harness @@
ステップ 6 --- クラッシュのトリアージ
# Reproduce and categorize crashes
for crash in findings/*/crashes/*; do
echo "=== Testing: $crash ==="
timeout 5 ./fuzz_harness_asan "$crash" 2>&1 | head -20
echo "---"
done
# Deduplicate crashes by stack trace
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 |
ファジングキャンペーンの監視
# View fuzzing statistics
afl-whatsup findings/
# Key metrics to track:
# - Total paths found (code coverage indicator)
# - Unique crashes / unique hangs
# - Stability percentage (should be >90%)
# - Exec speed (execs/sec)
# - Cycles done (full corpus cycles completed)