1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファジングってなんだ?〜AIが進化させた「壊して見つける」バグ発見の最前線〜

1
Posted at

この記事の対象読者

  • ソフトウェアテストの基礎知識がある方
  • セキュリティテストに興味があるが、ファジングを試したことがない方
  • LLMやAIがセキュリティ分野でどう活用されているか知りたい方

この記事で得られること

  • ファジングの基本概念と3つの主要手法(ミューテーション型/ジェネレーション型/カバレッジガイド型)を理解できる
  • Google OSS-Fuzzの実績データと、AIファジングが変えたゲーム: 1,300プロジェクト・13,000脆弱性発見の裏側がわかる
  • Pythonで実際にファジングを体験する方法: Atherisを使った実践コードとCI/CDへの組み込み方がわかる

この記事で扱わないこと

  • ファジングエンジンの内部実装(libFuzzer/AFL++のソースコード解説)
  • カーネルファジング・ハードウェアファジングなどの高度な専門領域
  • 脆弱性の悪用手法(攻撃側の具体的テクニック)

1. ファジングとの出会い

「20年間見つからなかった脆弱性を、AIがたった数週間で12個見つけた」

先日の記事で紹介した、OpenSSLの歴史的な事件。この発見の背景にあった技術が**ファジング(Fuzzing)**だ。

正直に言うと、私が初めてファジングという言葉を聞いたとき、「なんかランダムにデータを突っ込むやつでしょ?」程度の認識だった。テストと言えばユニットテストやE2Eテストが頭に浮かぶし、ファジングは「セキュリティの専門家がやるニッチなもの」というイメージがあった。

しかし、OpenSSLの事件を調べるうちに認識が変わった。ファジングは、2026年の今、AIの力で「全開発者が知るべきテスト手法」に変貌しつつある。

料理に例えるなら、ファジングは「レシピ通りに作れるか確認する味見」ではなく、**「わざと変な材料を入れて、キッチンが爆発しないか確認する破壊テスト」**だ。

ここまでで、ファジングがどんな位置づけの技術なのか、イメージが湧いたと思う。次は、この記事で使う用語を整理しておこう。


2. 前提知識の確認

本題に入る前に、この記事で登場する用語を確認する。

2.1 ファジング(Fuzzing)とは

ソフトウェアに対して、意図的に不正・予期しない・ランダムなデータを入力として与え、クラッシュや異常動作を引き起こすバグを発見する自動テスト手法。1988年にウィスコンシン大学のBarton Miller教授が、Unixユーティリティにランダムデータを入力する実験で提唱した。

2.2 ファザー(Fuzzer)とは

ファジングを実行するツールのこと。テスト対象に送る入力データを生成し、プログラムの異常動作を検出する。代表的なファザーにはGoogle開発のlibFuzzer、AFL++(American Fuzzy Lop plus plus)、Honggfuzzなどがある。

2.3 コードカバレッジとは

テスト実行時に、プログラムのソースコードのうちどの部分が実行されたかを示す指標。カバレッジが高いほど、多くのコードパスがテストされていることを意味する。ただし、カバレッジ100%でもバグがないとは限らない——これがファジングの重要な教訓だ。

2.4 サニタイザー(Sanitizer)とは

プログラム実行中のメモリエラーや未定義動作を検出するツール。AddressSanitizer(ASan)、MemorySanitizer(MSan)、UndefinedBehaviorSanitizer(UBSan)などがある。ファジングとサニタイザーを組み合わせることで、クラッシュしないが危険な動作(メモリリーク、バッファオーバーリードなど)も発見できる。

これらの用語が押さえられたら、ファジングが生まれた背景を見ていこう。


3. ファジングが生まれた背景

3.1 1988年:偶然から生まれたテスト手法

ファジングの起源は、1988年のウィスコンシン大学に遡る。Barton Miller教授が嵐の夜にリモート接続でUnixシステムを操作していたところ、回線のノイズでランダムな文字がコマンドに混入し、多くのUnixユーティリティがクラッシュした。この「偶然の発見」から、意図的にランダム入力でプログラムを壊すテスト手法が生まれた。

Miller教授のチームが最初の論文で報告した結果は衝撃的だった——テストしたUnixユーティリティの25〜33%がランダム入力でクラッシュした

3.2 2014年以降:Heartbleedが変えた世界

2014年、OpenSSLのHeartbleed脆弱性(CVE-2014-0160)が発見され、世界中のHTTPS通信が危機にさらされた。この事件をきっかけに、オープンソースソフトウェアのセキュリティ監査が大幅に強化された。

Googleは2016年にOSS-Fuzzプロジェクトを立ち上げ、オープンソースプロジェクトに対する無料の継続的ファジングサービスを開始した。

マイルストーン
1988年 Barton Miller教授がファジングを提唱
2006年 Google、Chromeのセキュリティにファジングを本格導入
2014年 Heartbleed発見、OSS監査の強化
2016年 Google OSS-Fuzz開始
2023年 Google、OSS-FuzzにAI(LLM)を統合
2024年 AI生成ファズターゲットで26件の脆弱性発見(OpenSSL含む)
2026年 AISLEのAIがOpenSSLで12件のゼロデイを発見

3.3 なぜ今ファジングが再注目されているのか

従来のファジングは「ランダムにデータを投げる力技」であり、膨大な計算資源と専門知識が必要だった。しかしAI/LLMの進化により、状況が根本的に変わりつつある。

従来のファジング AIファジング
ランダムな入力生成 コード構造を理解した入力生成
人間がファズターゲットを手動作成 LLMがファズターゲットを自動生成
クラッシュの分析は人間が行う AIがクラッシュを自動分類・トリアージ
専門家のみが運用可能 CI/CDに統合して全チームが利用可能

背景がわかったところで、基本的な仕組みを見ていこう。


4. 基本概念と仕組み

4.1 ファジングの3つの主要手法

ファジングは入力データの生成方法によって大きく3つに分類される。

ミューテーション型(Mutation-based)

既存の有効な入力データ(シード)を元に、ビット反転、バイト追加、値の変更などの変異(ミューテーション)を加えて新しい入力を生成する。最も一般的なアプローチ。

# ミューテーション型の概念イメージ
import random

def mutate(seed: bytes) -> bytes:
    """シードデータにランダムな変異を加える"""
    data = bytearray(seed)
    mutation_type = random.choice(["flip", "insert", "delete", "replace"])

    if mutation_type == "flip" and len(data) > 0:
        # ランダムなビットを反転
        pos = random.randint(0, len(data) - 1)
        data[pos] ^= (1 << random.randint(0, 7))

    elif mutation_type == "insert":
        # ランダムなバイトを挿入
        pos = random.randint(0, len(data))
        data.insert(pos, random.randint(0, 255))

    elif mutation_type == "delete" and len(data) > 1:
        # ランダムなバイトを削除
        pos = random.randint(0, len(data) - 1)
        del data[pos]

    elif mutation_type == "replace" and len(data) > 0:
        # ランダムなバイトを置換
        pos = random.randint(0, len(data) - 1)
        data[pos] = random.randint(0, 255)

    return bytes(data)

# 使用例
seed = b'{"name": "test", "value": 42}'
for i in range(5):
    mutated = mutate(seed)
    print(f"Mutation {i+1}: {mutated}")

ジェネレーション型(Generation-based)

入力データのフォーマット仕様(文法)を定義し、仕様に基づいて新しい入力をゼロから生成する。プロトコルファジングやファイルフォーマットのテストに有効。

カバレッジガイド型(Coverage-guided)

現在の主流。プログラムのコードカバレッジをフィードバックとして利用し、未到達のコードパスを探索するよう入力生成を最適化する。libFuzzerやAFL++がこの方式を採用している。

4.2 ファジングの動作フロー

┌─────────┐    ┌──────────┐    ┌──────────────┐    ┌─────────────┐
│ シード   │ →  │ 変異     │ →  │ テスト対象に │ →  │ 結果を監視  │
│ コーパス │    │ エンジン │    │ 入力を投入   │    │ (クラッシュ?)│
└─────────┘    └──────────┘    └──────────────┘    └──────┬──────┘
      ↑                                                    │
      │            カバレッジフィードバック                  │
      └────────────────────────────────────────────────────┘

4.3 AIが変えたファジングのパラダイム

2023年8月、GoogleはOSS-FuzzにLLMを統合した。AIは主に以下の領域でファジングを強化している。

AI活用領域 内容 効果
ファズターゲット自動生成 LLMがコードを分析し、テスト対象の関数を特定・ファズターゲットを自動生成 人間の作業を大幅に削減
入力文法の推論 AIが入力サンプルからフォーマット構造を学習 複雑なプロトコルのファジングが容易に
クラッシュトリアージ AIがクラッシュを自動分類し、重複排除・重大度判定 セキュリティチームの負担を削減
適応的変異戦略 強化学習で変異オペレータの選択を最適化 カバレッジの効率的な拡大

Google OSS-Fuzzは2025年5月時点で、1,300以上のプロジェクトで13,000以上の脆弱性と50,000以上のバグを発見している。

基本概念が理解できたところで、実際にコードを書いて動かしてみよう。


5. 実践:Pythonでファジングを体験する

5.1 環境構築

Pythonのファジングには、Googleが開発したAtheris(Pythonネイティブのカバレッジガイドファザー)を使用する。

# 必要なパッケージのインストール
pip install atheris hypothesis

5.2 環境別の設定ファイル

開発環境用(fuzzing_config.yaml)

# fuzzing_config.yaml - 開発環境用(このままコピーして使える)
environment: development
fuzzer:
  engine: atheris
  timeout_per_input: 5  # 秒
  max_total_time: 300   # 5分(開発中は短めに)
  corpus_dir: ./corpus/dev
  artifact_dir: ./crashes/dev
sanitizer:
  address: true
  undefined: true
reporting:
  level: DEBUG
  output: stdout

本番CI用(fuzzing_config.production.yaml)

# fuzzing_config.production.yaml - CI/CDパイプライン用
environment: production
fuzzer:
  engine: atheris
  timeout_per_input: 30
  max_total_time: 3600  # 1時間(CI/CDで十分な時間を確保)
  corpus_dir: ./corpus/ci
  artifact_dir: ./crashes/ci
  jobs: 4  # 並列実行数
sanitizer:
  address: true
  undefined: true
  memory: true
reporting:
  level: WARNING
  output:
    - file: ./fuzzing_report.json
    - slack_webhook: ${SLACK_SECURITY_WEBHOOK}
gate:
  fail_on_crash: true
  block_merge: true

テスト環境用(fuzzing_config.test.yaml)

# fuzzing_config.test.yaml - テスト/検証環境用
environment: test
fuzzer:
  engine: atheris
  timeout_per_input: 10
  max_total_time: 600  # 10分
  corpus_dir: ./corpus/test
  artifact_dir: ./crashes/test
sanitizer:
  address: true
  undefined: false  # テスト環境では速度優先
reporting:
  level: INFO
  output: ./fuzzing_test_report.json

5.3 基本的なファジング:JSON パーサーをテスト

"""
json_fuzzer.py - PythonのJSONパーサーに対するファジング
実行方法: python json_fuzzer.py
"""
import atheris
import sys
import json


def test_json_parser(data: bytes):
    """JSONパーサーに対するファズターゲット"""
    try:
        decoded = data.decode("utf-8", errors="ignore")
        # パース試行
        parsed = json.loads(decoded)
        # パース成功した場合、再シリアライズしてラウンドトリップチェック
        re_serialized = json.dumps(parsed)
        re_parsed = json.loads(re_serialized)
    except (json.JSONDecodeError, UnicodeDecodeError, ValueError):
        # これらの例外は「期待される失敗」なので無視
        pass
    except Exception as e:
        # 予期しない例外はバグの可能性
        raise


def main():
    """メイン処理"""
    atheris.Setup(
        sys.argv,
        test_json_parser,
        enable_python_coverage=True  # Pythonコードのカバレッジ計測を有効化
    )
    atheris.Fuzz()


if __name__ == "__main__":
    main()

5.4 実践的なファジング:自作バリデーターのバグを見つける

ここからが本番だ。意図的にバグを含む関数を書いて、ファジングで発見してみよう。

"""
validator_fuzzer.py - 自作バリデーターのバグをファジングで発見する
実行方法: python validator_fuzzer.py
"""
import atheris
import sys


def validate_user_input(data: str) -> dict:
    """
    ユーザー入力をバリデーションする関数(意図的にバグを含む)
    フォーマット: "name:age:email"
    """
    parts = data.split(":")

    if len(parts) != 3:
        raise ValueError("Invalid format: expected name:age:email")

    name, age_str, email = parts

    # バグ1: 名前の長さチェックが甘い(空文字を許容してしまう)
    if len(name) > 100:
        raise ValueError("Name too long")

    # バグ2: age のパースで整数オーバーフローの可能性
    age = int(age_str)  # 負の値や巨大な値を検証していない

    # バグ3: emailの@チェックが不完全
    if "@" not in email:
        raise ValueError("Invalid email")

    # バグ4: 特定の文字列でクラッシュ(シミュレーション)
    if name == "CRASH":
        result = [1, 2, 3]
        return result[999]  # IndexError!

    return {"name": name, "age": age, "email": email}


def test_validator(data: bytes):
    """バリデーターに対するファズターゲット"""
    try:
        input_str = data.decode("ascii", errors="ignore")
        result = validate_user_input(input_str)
    except (ValueError, UnicodeDecodeError):
        # 期待される検証エラーは無視
        pass
    except Exception as e:
        # ValueError以外の例外はバグ!
        print(f"[BUG FOUND] Input: {data!r}")
        print(f"[BUG FOUND] Exception: {type(e).__name__}: {e}")
        raise


def main():
    atheris.Setup(sys.argv, test_validator, enable_python_coverage=True)
    atheris.Fuzz()


if __name__ == "__main__":
    main()

5.5 実行結果

上記のコードを実行すると、以下のような出力が得られる:

$ python validator_fuzzer.py -max_total_time=60
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2847391056
INFO: -max_total_time is set to 60
...
INFO: New: 1 L: 15 MS: 1 CMP- DE: "CRASH"-
[BUG FOUND] Input: b'CRASH:25:a@b.com'
[BUG FOUND] Exception: IndexError: list index out of range
==12345== ERROR: libFuzzer: deadly signal
...
artifact_prefix='./'; Test unit written to ./crash-abc123def456

ファジングは数秒〜数分で「CRASH」という入力値を発見し、IndexErrorが発生することを報告してくれる。人間がテストケースを書いていたら見落としていたかもしれないバグだ。

5.6 よくあるエラーと対処法

エラー 原因 対処法
ModuleNotFoundError: No module named 'atheris' Atheris未インストール pip install atheris を実行
RuntimeError: Coverage not available Cython版Atherisがビルドできていない pip install atheris --no-binary atheris でソースビルド
BARF: No valid inputs in corpus コーパスディレクトリが空 初回はコーパスなしで実行可能。シードファイルを追加するとより効率的
timeout per unit exceeded 1入力あたりのタイムアウト超過 ファズターゲットの処理を軽量化するか、-timeout 値を増やす
Windows環境で atheris がインストールできない AtherisはLinux/macOS向け WSL2を使用するか、代替としてhypothesisを使用

5.7 環境診断スクリプト

#!/usr/bin/env python3
"""
ファジング環境診断スクリプト
実行方法: python check_fuzz_env.py
"""

import sys
import shutil
import subprocess


def check_environment():
    """ファジング環境をチェックして問題を報告"""
    issues = []

    # Python バージョン確認
    if sys.version_info < (3, 9):
        issues.append(f"Python 3.9以上が必要です(現在: {sys.version}")
    else:
        print(f"  [OK] Python {sys.version}")

    # Atheris確認
    try:
        import atheris
        print(f"  [OK] atheris がインストール済み")
    except ImportError:
        issues.append("atheris がインストールされていません: pip install atheris")

    # Hypothesis確認(プロパティベーステスト用)
    try:
        import hypothesis
        print(f"  [OK] hypothesis {hypothesis.__version__}")
    except ImportError:
        issues.append("hypothesis 未インストール(推奨): pip install hypothesis")

    # Clang/LLVM確認(C/C++ファジング用)
    if shutil.which("clang"):
        result = subprocess.run(["clang", "--version"], capture_output=True, text=True)
        version_line = result.stdout.split('\n')[0]
        print(f"  [OK] {version_line}")
    else:
        issues.append("clang が見つかりません(C/C++ファジングに必要)")

    # AFL++確認
    if shutil.which("afl-fuzz"):
        print("  [OK] AFL++ がインストール済み")
    else:
        issues.append("AFL++ 未インストール(推奨): apt install afl++")

    # サマリー
    print()
    if issues:
        print("  問題が見つかりました:")
        for issue in issues:
            print(f"    - {issue}")
    else:
        print("  環境は正常です。ファジングを始められます!")


if __name__ == "__main__":
    print("=" * 50)
    print("  ファジング環境診断ツール v1.0")
    print("=" * 50)
    check_environment()

5.8 GitHub Actionsでの自動ファジング(CI/CD統合)

# .github/workflows/fuzz.yml
name: Fuzzing CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  fuzz:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install atheris
          pip install -r requirements.txt

      - name: Run fuzzing (5 minutes)
        run: |
          python validator_fuzzer.py -max_total_time=300 -artifact_prefix=./crashes/

      - name: Upload crash artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: fuzzing-crashes
          path: ./crashes/

実装方法がわかったので、次は具体的なユースケースを見ていく。


6. ユースケース別ガイド

6.1 ユースケース1: Webアプリケーションの入力バリデーションテスト

想定読者: PythonやJavaScriptでWebアプリを開発している方

推奨構成: Hypothesis(プロパティベーステスト)+ pytest で、ユニットテストにファジング的アプローチを導入

サンプルコード:

"""
web_input_fuzz.py - Hypothesisを使ったWebアプリ入力のファジング
実行方法: pytest web_input_fuzz.py -v
"""
from hypothesis import given, strategies as st, settings
import re


def sanitize_username(username: str) -> str:
    """ユーザー名をサニタイズする関数(テスト対象)"""
    if not username or len(username) > 50:
        raise ValueError("Invalid username length")

    # 英数字とアンダースコアのみ許可
    sanitized = re.sub(r'[^a-zA-Z0-9_]', '', username)

    if len(sanitized) == 0:
        raise ValueError("Username contains no valid characters")

    return sanitized.lower()


@given(st.text(min_size=0, max_size=200))
@settings(max_examples=10000)
def test_sanitize_username_never_crashes(username):
    """サニタイズ関数がどんな入力でもクラッシュしないことを検証"""
    try:
        result = sanitize_username(username)
        # サニタイズ結果の事後条件チェック
        assert isinstance(result, str)
        assert len(result) <= 50
        assert result == result.lower()
        assert re.match(r'^[a-z0-9_]+$', result)
    except ValueError:
        pass  # ValueErrorは期待される失敗


@given(st.text(alphabet=st.characters(whitelist_categories=('L', 'N')),
               min_size=1, max_size=50))
def test_sanitize_valid_input_preserves_content(username):
    """有効な入力が正しくサニタイズされることを検証"""
    result = sanitize_username(username)
    assert len(result) > 0

6.2 ユースケース2: APIのエンドポイントファジング

想定読者: REST APIを開発・運用している方

推奨構成: Schemathesis(OpenAPI仕様ベースのAPIファジング)

サンプルコード:

# Schemathesisのインストール
pip install schemathesis

# OpenAPI仕様からAPIを自動ファジング
schemathesis run http://localhost:8000/openapi.json \
  --checks all \
  --stateful=links \
  --max-response-time=5000
"""
api_fuzz.py - Schemathesisを使ったAPI自動ファジング(Pythonスクリプト版)
"""
import schemathesis

schema = schemathesis.from_url("http://localhost:8000/openapi.json")

@schema.parametrize()
def test_api(case):
    """OpenAPI仕様に基づいてAPIを自動ファジング"""
    response = case.call()
    case.validate_response(response)
    # サーバーエラー(5xx)は許容しない
    assert response.status_code < 500

6.3 ユースケース3: データパイプラインの堅牢性テスト

想定読者: データエンジニアリングやML基盤を構築している方

推奨構成: Hypothesisの@givenデコレータでデータ変換関数をテスト

サンプルコード:

"""
data_pipeline_fuzz.py - データ変換関数のファジング
実行方法: pytest data_pipeline_fuzz.py -v
"""
from hypothesis import given, strategies as st
import math


def normalize_score(value: float, min_val: float, max_val: float) -> float:
    """スコアを0〜1の範囲に正規化する関数(テスト対象)"""
    if min_val >= max_val:
        raise ValueError("min must be less than max")
    if math.isnan(value) or math.isinf(value):
        raise ValueError("value must be finite")

    normalized = (value - min_val) / (max_val - min_val)
    return max(0.0, min(1.0, normalized))


@given(
    value=st.floats(allow_nan=True, allow_infinity=True),
    min_val=st.floats(min_value=-1e6, max_value=1e6),
    max_val=st.floats(min_value=-1e6, max_value=1e6),
)
def test_normalize_score_handles_all_floats(value, min_val, max_val):
    """正規化関数がどんなfloat値でも安全に処理できることを検証"""
    try:
        result = normalize_score(value, min_val, max_val)
        assert 0.0 <= result <= 1.0, f"Result {result} out of range"
        assert not math.isnan(result), "Result is NaN"
    except ValueError:
        pass  # 期待される検証エラー

ユースケースを把握できたところで、この先の学習パスを確認しよう。


7. 学習ロードマップ

この記事を読んだ後、次のステップとして以下をおすすめする。

初級者向け(まずはここから)

  1. Hypothesis公式ドキュメントでプロパティベーステストの基本を学ぶ
  2. Google OSS-Fuzzの公式リポジトリでプロジェクト一覧と実績を確認する
  3. 自分のプロジェクトの入力バリデーション関数にHypothesisを適用してみる

中級者向け(実践に進む)

  1. Atherisで自分のPythonプロジェクトのファズターゲットを作成する
  2. SchemathesisでREST APIのファジングを導入する
  3. GitHub ActionsやGitLab CIにファジングジョブを組み込む

上級者向け(さらに深く)

  1. AFL++でC/C++プロジェクトのファジングに挑戦する
  2. Google FuzzBenchでファジングエンジンの性能比較を学ぶ
  3. ClusterFuzzLiteで自分のプロジェクトにOSS-Fuzz相当の環境を構築する

8. まとめ

この記事では、ファジングについて以下を解説した。

  1. ファジングの基本概念と歴史: 1988年の偶然の発見から、2026年のAI統合まで
  2. 3つの主要手法: ミューテーション型、ジェネレーション型、カバレッジガイド型の違い
  3. Pythonでの実践方法: Atheris/Hypothesisを使った具体的なコードとCI/CD統合

私の所感

OpenSSLの12件のゼロデイ発見を調べる中で、ファジングの進化に驚かされた。特にGoogleのOSS-Fuzzが「OpenSSLに対して139個のファザーを並行運用している」というデータは衝撃的だった。

しかし同時に、GitHub Security Labの研究者が指摘するように、OSS-Fuzzに登録されていても、カバレッジが低いプロジェクトでは脆弱性が何年も見逃されている。GStreamerのカバレッジはわずか19%で、7年間ファジングされていたにもかかわらず29件の新規脆弱性が発見された。

ファジングは「導入して終わり」ではない。ファズターゲットの継続的な改善、カバレッジの監視、新しい攻撃面の追加——この地味な運用こそが、本当のセキュリティを支えている。

そしてAIは、この「地味な運用」を自動化する力を持っている。私の見解では、2026年以降、AIファジングはユニットテストと同じくらい「当たり前のテスト手法」になると考えている。


参考文献


この記事が役に立ったら、いいね・ストックしていただけると励みになります。

関連記事:


X(Twitter)でもAI/セキュリティ系の情報を発信中 → @geneLab_999

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?