Go言語入門:ファジングについて

スポンサーリンク
Go言語入門:ファジングについて ノウハウ
Go言語入門:ファジングについて
この記事は約31分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、Golangにおけるファジングの基本をご紹介します。

スポンサーリンク

概要

ファジングでは、ランダムなデータをテストに対して実行し、脆弱性やクラッシュの原因となる入力を見つけようとします。ファジングで見つかる脆弱性の例としては、SQL インジェクション、バッファオーバーフロー、サービス拒否、クロスサイトスクリプティング攻撃などがあります。

この記事では、簡単な関数のファジングテストを書き、go コマンドを実行し、コードの問題をデバッグして修正します。

下記のセクションで構成されています。

  1. コード用のフォルダを作成します。
  2. テストするコードを追加します。
  3. ユニットテストを追加します。
  4. ファズテストを追加する。
  5. 2つのバグを修正する。
  6. 追加のリソースを調べる。

Goファジングは現在、Go Fuzzingのドキュメントに記載されている組み込み型のサブセットをサポートしており、今後さらに組み込み型のサポートが追加される予定です。ドキュメントのURLは下記になります。

コード用のフォルダを作成する

最初に作業ディレクトリを作成します。手順は下記のコマンドになります。

mkdir 16_learn-golang-fuzz
cd 16_learn-golang-fuzz

asdfコマンドで使用するGolangのバージョンを指定します。すでにGolangが実行できる状態にある方は、このコマンドをスキップしても問題ありませんが、Go 1.18以降である必要があります。

asdf local golang 1.20.5

モジュールの初期化を初期化します。この例では、fuzzモジュールを作成します。

go mod init example/fuzz

コードの追加

このセクションでは、文字列を反転させる関数を追加します。

テキストエディタを使って、main.goというファイルを作成します。

main.goのファイルの一番上に、以下のパッケージ宣言を貼り付けます。

package main

パッケージ宣言の下に、以下の関数宣言を貼り付けます。

func Reverse(s string) string {
    b := []byte(s)
    for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b)
}

main.goの一番上、パッケージ宣言の下に、文字列を初期化し、それを反転させ、出力を表示し、それを繰り返す以下のmain関数を貼り付けます。

func main() {
    // Initialize a map for the integer values
    ints := map[string]int64{
        "first":  34,
        "second": 12,
    }

    // Initialize a map for the float values
    floats := map[string]float64{
        "first":  35.98,
        "second": 26.99,
    }

    fmt.Printf("Non-Generic Sums: %v and %v\n",
        SumInts(ints),
        SumFloats(floats))
}

main.goの一番上、パッケージ宣言のすぐ下に、今書いたコードをサポートするために必要なパッケージをインポートします。

コードの最初の行は次のようにします。

package main

import "fmt"

main.goを保存します。

main.goのあるディレクトリのコマンドラインから、コードを実行する。

go run .

下記のような出力になっていれば成功です。

% go run .
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed again: "The quick brown fox jumped over the lazy dog"

元の文字列、それを反転させた結果、そして再び反転させた結果、元の文字列と等価な文字列を見ることができます。

単体テストの追加

このセクションでは、Reverse関数の基本的なユニットテストを記述します。

テキストエディタを使って、fuzzディレクトリにreverse_test.goというファイルを作成します。

reverse_test.goに以下のコードを貼り付けます。

package main

import (
    "testing"
)

func TestReverse(t *testing.T) {
    testcases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {" ", " "},
        {"!12345", "54321!"},
    }
    for _, tc := range testcases {
        rev := Reverse(tc.in)
        if rev != tc.want {
                t.Errorf("Reverse: %q, want %q", rev, tc.want)
        }
    }
}


go testを使ってユニットテストを実行します。

go test

下記のような出力になっていれば成功です。

% go test
PASS
ok      example/fuzz    0.273s

ファズテストの追加


単体テストには限界があります。それは、それぞれの入力を開発者がテストに追加しなければなりません。ファジングの利点の1つは、あなたのコードに対する入力を考え出すことであり、あなたが考え出したテストケースが到達しなかったエッジケースを特定するかもしれないことです。

このセクションでは、単体テストをファズテストに変換し、より少ない作業でより多くの入力を生成できるようにします。

ユニットテスト、ベンチマーク、ファズテストを同じ *_test.go ファイルに保存することができますが、この例ではユニットテストをファズテストに変換します。

テキストエディタで、reverse_test.goのユニットテストを以下のファズテストに置き換えてください。

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

ファジングにもいくつかの限界がある。単体テストでは、リバース関数の期待される出力を予測し、実際の出力がその期待値を満たしているかどうかを検証することができます。

例えば、Reverse(“Hello, world”)というテストケースのユニットテストでは、戻り値を “dlrow ,olleH “と指定しています。

ファジングでは、入力を制御できないので、期待される出力を予測することはできません。

しかし、ファズテストで検証できるReverse関数のプロパティがいくつかあります。このファズテストでチェックする2つのプロパティは以下の通りです。

  • 文字列を2回反転すると、元の値が保持される。
  • 反転された文字列は、有効なUTF-8の状態を保持します。

ユニットテストとファズテストの構文の違いに注意してください。

関数はTestXxxの代わりにFuzzXxxで始まり、*testing.Tの代わりに*testing.Fを取ります。
t.Runの実行が期待されるところでは、代わりにf.Fuzzがあり、これは*testing.Tとファジングされる型をパラメータとするファジングターゲット関数を取ります。単体テストからの入力は、f.Addを使用してシードコーパスの入力として提供されます。
新しいパッケージunicode/utf8がインポートされていることを確認してください。


go testを使ってユニットテストを実行します。

go test

下記のような出力になっていれば成功です。

% go test
PASS
ok      example/fuzz    0.273s

FuzzReverseをファジング付きで実行し、ランダムに生成された文字列入力が失敗を引き起こすかどうかを確認します。これは、パラメータFuzzに新しいフラグ-fuzzを設定したgo testを使用して実行されます。以下のコマンドを実行してください。

go test -fuzz=Fuzz

もうひとつの便利なフラグは -fuzztime で、これはファジングにかかる時間を制限する。例えば、上記のテストで -fuzztime 10s を指定すると、それ以前に失敗が発生しない限り、テストはデフォルトで 10 秒経過後に終了します。その他のテストフラグについては、cmd/go ドキュメントのこのセクションを参照してください。

下記のような出力になっていれば、想定通りです。

% go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 10 workers
fuzz: elapsed: 0s, execs: 1741 (55535/sec), new interesting: 6 (total: 9)
--- FAIL: FuzzReverse (0.03s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:20: Reverse produced invalid UTF-8 string "\x87\xc3"
    
    Failing input written to testdata/fuzz/FuzzReverse/c8738a8dfd6ed5a1
    To re-run:
    go test -run=FuzzReverse/c8738a8dfd6ed5a1
FAIL
exit status 1
FAIL    example/fuzz    0.306s

ファジング中に失敗が発生し、その原因となった入力が、-fuzz フラグがなくても、次に go テストが呼ばれたときに実行されるシードコーパスファイルに書き込まれます。失敗の原因となった入力を見るには、testdata/fuzz/FuzzReverseディレクトリに書き込まれたコーパスファイルをテキストエディタで開きます。シードとなるコーパスファイルには別の文字列が含まれているかもしれませんが、書式は同じです。

go test fuzz v1
string("Ç")

コーパスファイルの最初の行はエンコーディングのバージョンを示す。それに続く各行はコーパスエントリを構成する各タイプの値を表す。ファズ・ターゲットは1つの入力しか受け取らないので、バージョンの後には1つの値しかない。

fuzzフラグを付けずにもう一度go testを実行すると、失敗した新しいシードコーパスのエントリーが使われます。

% go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/c8738a8dfd6ed5a1 (0.00s)
        reverse_test.go:20: Reverse produced invalid UTF-8 string "\x87\xc3"
FAIL
exit status 1
FAIL    example/fuzz    0.344s

エラーの修正

このセクションでは、失敗をデバッグし、バグを修正します。

次に進む前に、この問題について考えたり、自分で問題を解決してみたりすることに時間を費やしてください。

このエラーをデバッグするには、いくつかの方法がある。VS Codeをテキストエディタとして使用している場合は、デバッガをセットアップして調査することができます。

このセクションでは、デバッグに役立つ情報をターミナルに記録します。

まず、utf8.ValidString.ValidStringのドキュメントを参照してください。

ValidString reports whether s consists entirely of valid UTF-8-encoded runes.

現在のReverse関数は文字列をバイト単位で反転させるが、そこに問題がある。元の文字列のUTF-8エンコードされたルーンを保持するためには、文字列をルーン単位で反転させなければならない。

入力(この場合は”Ç”)が、Reverseが反転したときに無効な文字列を生成する原因になっている理由を調べるには、反転した文字列のルーン数を調べればよい。

コードを書く
テキストエディタで、FuzzReverse内のファズターゲットを以下のように置き換える。

f.Fuzz(func(t *testing.T, orig string) {
    rev := Reverse(orig)
    doubleRev := Reverse(rev)
    t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))
    if orig != doubleRev {
        t.Errorf("Before: %q, after: %q", orig, doubleRev)
    }
    if utf8.ValidString(orig) && !utf8.ValidString(rev) {
        t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
    }
})

go testを使用してテストを実行する。

% go test
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/c8738a8dfd6ed5a1 (0.00s)
        reverse_test.go:18: Number of runes: orig=1, rev=2, doubleRev=1
        reverse_test.go:23: Reverse produced invalid UTF-8 string "\x87\xc3"
FAIL
exit status 1
FAIL    example/fuzz    1.342s

シードコーパス全体では、すべての文字が1バイトの文字列が使われていた。しかし、”Ç”のような文字は複数バイトを必要とすることがある。したがって、文字列をバイトごとに反転させると、マルチバイト文字が無効になる。

注:Goが文字列をどのように扱うか興味がある方は、ブログ記事「Goの文字列、バイト、ルーン、文字」を読むと理解が深まります。

バグの理解が深まったら、Reverse関数のエラーを修正してください。


Reverse関数を修正するために、文字列をバイト単位ではなくルーン単位でトラバースしてみましょう。

func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

主な違いは、リバースが各バイトではなく、文字列の各ルーンに対して反復処理を行うようになったことです。


go testを使ってテストを実行する。


go testを使ってユニットテストを実行します。

go test

下記のような出力になっていれば成功です。

% go test
PASS
ok      example/fuzz    0.282s

go test -fuzzでもう一度ファズして、新しいバグがないか確認してみましょう。

% go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
fuzz: minimizing 41-byte failing input file
fuzz: elapsed: 0s, gathering baseline coverage: 5/10 completed
--- FAIL: FuzzReverse (0.01s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:18: Number of runes: orig=1, rev=1, doubleRev=1
        reverse_test.go:20: Before: "\xc5", after: "�"
    
    Failing input written to testdata/fuzz/FuzzReverse/fccc053b3df38e81
    To re-run:
    go test -run=FuzzReverse/fccc053b3df38e81
FAIL
exit status 1
FAIL    example/fuzz    0.237s

文字列は2回反転された後、元の文字列とは異なっていることがわかる。今回は入力自体が無効なunicodeである。文字列でファジングしているのに、どうしてこんなことが可能なのでしょうか?

もう一度デバッグしてみましょう。

次に進む前に、この問題について考えたり、自分で問題を解決してみたりしてください。

二重反転エラーを修正

このセクションでは、二重反転の失敗をデバッグし、バグを修正する。

前と同じように、このエラーをデバッグする方法はいくつかあります。

このチュートリアルでは、Reverse関数にデバッグに役立つ情報を記録します。

エラーを見つけるために、反転された文字列をよく見てください。Goでは、文字列は読み取り専用のバイトスライスであり、有効なUTF-8でないバイトを含むことができます。元の文字列は’˶xc5’という1バイトのバイトスライスです。入力文字列を[]runeに設定すると、GoはバイトスライスをUTF-8にエンコードし、バイトをUTF-8文字�で置き換えます。置換されたUTF-8文字と入力バイトスライスを比較すると、両者は明らかに等しくありません。


テキストエディタで、Reverse関数を以下のように置き換えてください。

func Reverse(s string) string {
    fmt.Printf("input: %q\n", s)
    r := []rune(s)
    fmt.Printf("runes: %q\n", r)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

これは、文字列をルーンのスライスに変換する際に何が間違っているのかを理解するのに役立つでしょう。

今回は、ログを調べるために、失敗したテストだけを実行したい。そのために、go test -runを使います。

FuzzXxx/testdata内の特定のコーパスエントリを実行するには、-runに{FuzzTestName}/{ファイル名}を指定します。これはデバッグのときに便利です。この場合は -run フラグに失敗したテストのハッシュを指定します。一意のハッシュをターミナルからコピー&ペーストしてください。

% go test -run=FuzzReverse/fccc053b3df38e81 
input: "\xc5"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)
    --- FAIL: FuzzReverse/fccc053b3df38e81 (0.00s)
        reverse_test.go:18: Number of runes: orig=1, rev=1, doubleRev=1
        reverse_test.go:20: Before: "\xc5", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.085s

入力が無効なunicodeであることがわかったので、Reverse関数でエラーを修正しよう。

この問題を解決するために、Reverseへの入力が有効なUTF-8でない場合にエラーを返すようにしましょう。

テキストエディタで、既存のReverse関数を以下のように置き換えてください。

func Reverse(s string) (string, error) {
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}

この変更により、入力文字列に有効なUTF-8でない文字が含まれる場合はエラーが返される。

Reverse関数はエラーを返すようになったので、余分なエラー値を破棄するようにmain関数を修正してください。既存のmain関数を以下のように置き換えてください。

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

これらのReverseの呼び出しは、入力文字列が有効なUTF-8であるため、nilエラーを返すはずである。

errorsとunicode/utf8パッケージをインポートする必要がある。main.goのimport文は以下のようにする。

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

reverse_test.goファイルを修正してエラーをチェックし、returnしてエラーが発生した場合はテストをスキップする。

func FuzzReverse(f *testing.F) {
    testcases := []string {"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        if err1 != nil {
            return
        }
        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
             return
        }
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}

リターンするのではなく、t.Skip()を呼び出して、そのファズ入力の実行を停止することもできます。

go testを使用してテストを実行します。

% go test
PASS
ok      example/fuzz    0.302s

go test -fuzz=Fuzzでファズし、数秒経過したらctrl-Cでファズを停止する。fuzztimeフラグを渡さない限り、fuzzテストは失敗入力に遭遇するまで実行される。デフォルトでは、失敗がなければ永遠に実行され、ctrl-Cで処理を中断することができる。

% go test
PASS
ok      example/fuzz    0.302s

go test -fuzz=Fuzzでファズし、数秒経過したらctrl-Cでファズを停止する。fuzztimeフラグを渡さない限り、fuzzテストは失敗入力に遭遇するまで実行される。デフォルトでは、失敗がなければ永遠に実行され、ctrl-Cで処理を中断することができる。

% go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/11 completed
fuzz: elapsed: 0s, gathering baseline coverage: 11/11 completed, now fuzzing with 10 workers
fuzz: elapsed: 3s, execs: 1112834 (370916/sec), new interesting: 31 (total: 42)
fuzz: elapsed: 6s, execs: 2484442 (457080/sec), new interesting: 34 (total: 45)
fuzz: elapsed: 9s, execs: 4077765 (531226/sec), new interesting: 34 (total: 45)
fuzz: elapsed: 12s, execs: 5584411 (502269/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 15s, execs: 6929176 (448239/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 18s, execs: 8349896 (473537/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 21s, execs: 9834454 (494738/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 24s, execs: 11301398 (489068/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 27s, execs: 12759009 (485860/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 30s, execs: 14213783 (484901/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 33s, execs: 15723977 (503384/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 36s, execs: 17199740 (491922/sec), new interesting: 35 (total: 46)
fuzz: elapsed: 39s, execs: 18618089 (472884/sec), new interesting: 35 (total: 46)
^Cfuzz: elapsed: 41s, execs: 19592391 (473969/sec), new interesting: 35 (total: 46)
PASS
ok      example/fuzz    41.312s

go test -fuzz=Fuzz -fuzztime 30sでファズすると、30秒間ファズしてから、失敗が見つからなければ終了する。

% go test -fuzz=Fuzz -fuzztime 30s
fuzz: elapsed: 0s, gathering baseline coverage: 0/46 completed
fuzz: elapsed: 0s, gathering baseline coverage: 46/46 completed, now fuzzing with 10 workers
fuzz: elapsed: 3s, execs: 1313467 (437758/sec), new interesting: 2 (total: 48)
fuzz: elapsed: 6s, execs: 2740107 (475586/sec), new interesting: 2 (total: 48)
fuzz: elapsed: 9s, execs: 4164829 (474889/sec), new interesting: 2 (total: 48)
fuzz: elapsed: 12s, execs: 5673236 (502840/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 15s, execs: 7209170 (511875/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 18s, execs: 8625721 (472246/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 21s, execs: 10090388 (488166/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 24s, execs: 11605631 (505183/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 27s, execs: 13104948 (499592/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 30s, execs: 14510179 (468496/sec), new interesting: 3 (total: 49)
fuzz: elapsed: 30s, execs: 14510179 (0/sec), new interesting: 3 (total: 49)
PASS
ok      example/fuzz    30.174s

ファジングは合格!

fuzzフラグに加えて、いくつかの新しいフラグがgo testに追加された。

ファジングの出力で使われる用語の詳細についてはGo Fuzzingを参照してください。例えば、”new interesting “は、既存のファズテストコーパスのコードカバレッジを拡大する入力を指します。”new interesting “入力の数は、ファジングが始まると急激に増加し、新しいコードパスが発見されると数回急増し、その後時間の経過とともに減少すると予想されます。

解説

ファジング(fuzzing)は、ソフトウェアやシステムのテスト手法の一つであり、自動化されたランダムなデータを入力としてプログラムに与え、予期しない振る舞いやセキュリティの問題を特定するための手法です。ファジングは、ソフトウェアの品質向上やセキュリティの改善に役立つことがあります。

以下に、ファジングの基本的な概念と手法について説明します。

  1. ランダムなデータ生成: ファジングでは、自動化されたツールを使用してランダムなデータを生成します。このランダムなデータは、プログラムに入力として提供されます。データ生成方法は、ランダムバイト列や特定のデータ形式に基づく生成規則を使用することがあります。
  2. 入力の変異: ランダムなデータ生成に加えて、ファジングツールは生成されたデータに対してさまざまな変異(ミューテーション)を適用します。変異には、ビットフリップ(ランダムなビットの反転)、データの挿入や削除、数値の変更などが含まれます。これにより、さまざまな入力パターンを試すことができます。
  3. クラッシュや異常終了の検出: ファジング実行中に、プログラムがクラッシュしたり異常終了したりする場合があります。これは、バグやセキュリティの問題を示す可能性があります。ファジングツールは、クラッシュや異常終了が発生した場合にそれを検出し、報告します。
  4. テストカバレッジの追跡: ファジングでは、プログラムのテストカバレッジ(実行されたコードの割合)も追跡されます。これにより、プログラムのどの部分がテストされたかを可視化し、未テストの箇所を特定することができます。
  5. バグの報告と修正: ファジング実行中に検出されたクラッシュや異常終了は、バグトラッカーや開発者に報告されます。開発者は、報告された問題を調査し、修正を行うことができます。

ファジングは、ソフトウェアやプロトコル、ネットワークなど、さまざまな領域で利用されています。以下に、ファジングの応用例と追加の手法について説明します。

  1. ソフトウェアファジング: ソフトウェアファジングは、主にアプリケーションやライブラリのテストに使用されます。これにより、予期しないクラッシュやメモリリークなどのバグを特定することができます。ソフトウェアファジングの手法として、ブラックボックスファジング(入力データをプログラムの内部構造を知らないままに生成する)やホワイトボックスファジング(プログラムの内部構造や制御フローを利用してより効果的なテストケースを生成する)があります。
  2. プロトコルファジング: プロトコルファジングは、ネットワークプロトコルや通信プロトコルのセキュリティテストに使用されます。これにより、予期しない振る舞いやセキュリティの脆弱性を特定することができます。プロトコルファジングでは、入力メッセージやパケットのフィールドをランダム化して送信し、プロトコルの異常な振る舞いを引き出すことが目的です。
  3. Webファジング: Webファジングは、Webアプリケーションのセキュリティテストに使用されます。ランダムなHTTPリクエストやフォームデータを送信し、アプリケーションの挙動やセキュリティの
  4. マットグローブファジング(マイテーション駆動テスト): マットグローブファジングは、ランダムなデータ生成に加えて、特定のアプリケーションやプロトコルの内部構造や仕様に基づいて変異を行う手法です。これにより、より有効なテストケースを生成し、特定のコードパスや条件分岐を探索することができます。この手法は、より効率的なテストカバレッジの獲得や特定の脆弱性の発見に役立ちます。
  5. マルチスレッドファジング: マルチスレッドファジングは、複数のスレッドを使用して同時に複数のテストケースを実行する手法です。これにより、競合状態やデッドロック、クラッシュなど、マルチスレッド環境での問題を特定することができます。マルチスレッドファジングは、並行処理を多用するアプリケーションやシステムのテストに特に有効です。
  6. グレイボックスファジング: グレイボックスファジングは、ブラックボックスファジングとホワイトボックスファジングの組み合わせです。プログラムの外部挙動をブラックボックスとして扱いつつ、内部のコードや制御フローをホワイトボックスとして利用します。これにより、より効果的なテストケースを生成し、プログラムの内部の重要なパスを探索することができます。

ファジングは、自動化されたテスト手法であり、大量のデータを効率的にテストすることができます。しかし、完全なセキュリティの保証を提供するわけではありません。ファジングは、他のテスト手法と組み合わせて使用することが重要です。また、ファジングによって見つかった問題を適切に報告し、修正を行うことも重要なプロセスです。

おわりに

おわりに

この記事では、Goでのファジングを紹介しました。

次のステップは、コードの中からファジングしたい関数を選び、試してみることです!ファジングでバグが見つかったら、トロフィーケースに追加してみましょう。

何か問題が発生したり、機能のアイデアがある場合は、issueを提出してください。

この機能についての議論や一般的なフィードバックは、Gophers Slackの#fuzzingチャンネルに参加することもできます。

詳しくはgo.dev/security/fuzzのドキュメントをご覧ください。

本記事で使用したコードは下記のリポジトリにあります。

Golangが初めての方は、Effective Go と How to write Go codeに役立つベストプラクティスが記載されています。

また、Go ツアーというGolang の基礎をステップバイステップで学べる入門サイトもあります。

よっしー
よっしー

何か質問や相談があれば、遠慮なくコメントしてください。また、エンジニア案件についても、いつでも相談にのっていますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました