模糊測試是一種測試技術,會自動把非預期的輸入餵給軟體,以找出缺陷。本頁說明其運作方式,以及如何開始測試自己的程式碼(不涵蓋測試他人的系統)。
什麼是模糊測試
OWASP 將模糊測試描述為一種軟體測試技術,目的是找出錯誤、弱點或非預期的行為。它會自動提供無效、非預期或部分無效的輸入,並觀察程式是否出現異常行為。NIST 詞彙表(引用 NIST SP 800-95)同樣將模糊測試描述為:由稱為 fuzzer 的工具,將各種輸入組合提交給測試目標,以揭示其反應方式。
它的優點在於對系統應有的行為做的假設很少。人工撰寫的測試只涵蓋撰寫者想到的情況,而模糊測試工具可能找出在無人設想的輸入下會失效的地方。
運作方式
多數現代模糊測試工具都是覆蓋率導向的。它們會測量哪些程式碼被執行,保留能觸及新程式碼的輸入,並加以變異以產生下一批輸入。這樣能比單純的隨機輸入深入程式更多部分。LLVM 的 libFuzzer 文件將它描述為一種行程內、覆蓋率導向、演化式的模糊測試引擎,會連結到被測試的函式庫中。
常見工具
| 工具 | 主要目標 | 說明 |
|---|---|---|
| libFuzzer | C/C++ 及其他語言(LLVM) | 連結到目標程式中。根據其文件,原作者已轉向 Centipede 引擎,libFuzzer 現在只接收重要的錯誤修正 |
| AFL++ | C/C++ 及其他語言 | 廣泛使用的覆蓋率導向模糊測試工具 |
| Go 內建模糊測試 | Go(1.18 或更新版本) | 使用 go test -fuzz 執行。造成失敗的輸入會儲存在 testdata 下,並由一般測試重播 |
| OSS-Fuzz | 開源軟體 | Google 提供的免費持續模糊測試服務,使用多種引擎與 sanitizer |
libFuzzer 的模糊測試目標是一個函式(LLVMFuzzerTestOneInput),會接收輸入的位元組及其長度。在 Go 中,你要撰寫名為 FuzzXxx 的測試函式,它接收 *testing.F,並以 f.Add 加入初始輸入。
如何開始測試自己的軟體
- 選擇一個解析輸入的函式(檔案格式解析器、設定載入器、協定處理器)
- 撰寫一個小型模糊測試目標,只把輸入傳給該函式。保持它快速且具決定性,呼叫之間不保留狀態
- 加入幾個典型的有效輸入作為初始種子
- 開啟 AddressSanitizer 等 sanitizer,並執行設定好的時間(在 Go 中,
-fuzztime用來設定長度) - 將每個造成失敗的輸入轉為可重現的測試,並在修正後保留為回歸測試
- 若要持續進行,可在 CI 中執行短時段的測試;若是開源專案,可考慮加入 OSS-Fuzz
Go 的模糊測試每次執行有 1 秒的限制,而覆蓋率引導可在 AMD64 與 ARM64 上運作。請查閱官方文件,確認你的環境中的限制。
只對你管理的軟體進行模糊測試
模糊測試會持續傳送大量格式錯誤的輸入。若對你沒有測試權限的服務或伺服器這樣做,可能構成未授權存取或服務中斷。請將測試範圍限制在你開發或管理、位於自己環境中的軟體。
能做什麼,不能做什麼
OWASP 指出,模糊測試是對人工程式碼審查與除錯的補充,而非取代。它也提到,模糊測試工具傾向找出較簡單的錯誤,而且在黑箱測試中,較難判斷發現的影響程度。
因此,模糊測試擅長的是「讓機器搜尋程式可能出錯的方式」,而對於設計層級的問題,例如缺少授權檢查,效果則較弱。對於這類問題,例如 IDOR,應依賴檢查預期規則的測試。
本站觀點:找到輸入只是一半的工作,還要保留為回歸測試
重點不只是執行模糊測試工具,而是把它找到的輸入保存為回歸測試。Go 內建的模糊測試會把造成失敗的輸入儲存在 testdata 下,並在一般測試中重播,就是遵循這個理念。它能避免已修正的錯誤在之後的變更中再次出現。如果你負責維護解析輸入的程式碼,一個務實的第一步是只對單一函式進行模糊測試。
主要來源
- OWASP Community,《Fuzzing》— owasp.org(定義、特性、限制)
- NIST CSRC 詞彙表,「fuzz testing」— csrc.nist.gov(來源:NIST SP 800-95)
- LLVM,《libFuzzer》— llvm.org(運作方式、模糊測試目標、維護狀態)
- Go,《Fuzzing》— go.dev(Go 1.18 或更新版本、執行方式、限制)
- Google,《OSS-Fuzz》— google.github.io(概述、支援的引擎)。錯誤數量會隨時間變化,因此此處不引用數字。至於 AFL++,請查閱其官方文件以了解目前的用法