谷歌開源模糊測試工具 ClusterFuzz 嚐鮮記錄
背景
模糊測試,是指用隨機壞資料(也稱做 fuzz)攻擊一個程式,然後等著觀察哪裡遭到了破壞。(出自 模糊測試 )。一直以來都有不少的模糊測試工具,但大多隻集中在資料生成,執行和異常檢測依賴人工,未有比較完整的方案。
早在八年前,google 內部就在建設和使用模糊測試的工具來測試其內部的應用,而在兩年前, google 推出了 OSS-Fuzz 服務,用於給開源專案的進行免費的模糊測試服務,可自動在新版本程式碼提交後自動完成 測試->異常檢測->issue登記->老版本issue迴歸及自動關閉 的功能。背後使用的就是 ClusterFuzz 技術。流程圖如下:
而在過年前,google 開源了 ClusterFuzz ,並解決了原有 ClusterFuzz 必須依賴 Google Cloud 提供的服務這個問題,提供了本地執行的解決方案。根據官方介紹,它具備如下功能:
- 高度可擴充套件,谷歌的內部例項執行在超過 25000 臺機器上
- 準確的去副本化(Accurate deduplication)
- 問題跟蹤器的全自動錯誤歸檔和關閉
- 最小化測試用例
- 通過二分法迴歸查詢
- 提供分析 fuzzer 效能和崩潰率的統計資訊(不支援本地部署)
- 易於使用的 Web 介面,用於管理和檢視崩潰
- 支援引導模糊(例如 libFuzzer 和 AFL)和黑盒模糊測試
其大致執行流程如下:
當然,方案並不完美,如模糊資料統計、崩潰資料統計等功能由於依賴 google cloud 強大的資料處理能力,本地執行時是用不了的。
官方說的總是美好的,現實是否這麼完美呢?曾有人說,實踐是檢驗真理的唯一標準,為了更好地瞭解這個工具,當然就要本地跑個 demo 玩下啦。
本地搭建及執行
要獲得 ClusterFuzz 的完整功能,需要連線 Google Cloud Platform 。但結合國情,我們更期望瞭解它純本地執行能做到什麼,因此這次嚐鮮主要嘗試純本地執行。
注意:雖然執行可以脫離 Google Cloud Platform ,但部分安裝時用到的工具需要到 google 站點下載,所以,你懂得。
以下步驟均是在 macOS 10.14 上進行。
環境搭建
1、下載原始碼
git clone https://github.com/google/clusterfuzz cd clusterfuzz
2、安裝 google cloud sdk
進入 https://cloud.google.com/sdk/ ,按照引導安裝 sdk 並配置好環境變數(mac 下可以直接用解壓後的 install.sh
指令碼一鍵安裝),確認命令列可呼叫 gcloud 命令
$ gcloud -v Google Cloud SDK 226.0.0 bq 2.0.38 core 2018.11.16 gsutil 4.34
3、安裝 python 和 go 執行環境。
特別注意:如果你使用的是 macOS 或者 Ubuntu、Debain,直接執行第4步即可,腳本里會自動安裝 Python 和 go
python 要求 2.7.10 以上,但不能是 python 3。在 mac 上可以直接執行 brew install python@2
安裝。
go 未要求版本,在 mac 上可以直接執行 brew install go
安裝。我用的是 go1.11.5 darwin/amd64
4、安裝其他依賴
針對
- Ubuntu (14.04, 16.04, 17.10, 18.04, 18.10)
- Debian 8 (jessie) or later
- Recent versions of macOS with homebrew (experimental)
幾個系統,官方已經內建了安裝依賴的指令碼,直接執行即可:
local/install_deps.bash
坑一,官方的腳本里第一行用了 -ex 引數,會導致執行指令碼時如果有命令執行出錯(如 brew install 時有些應用本地已經安裝過,但非最新版本),直接退出程式。
可以通過 sed -i '' 's/bash -ex/bash -x/' local/install_deps*
命令直接去掉 -e 引數。已經給官方提了 issue
坑二,官方腳本里使用 python butler.py bootstrap
初始化環境時,會自動去 google 站點下載 chromedriver 相關的檔案。
全域性搜尋了下原始碼,只有跑單測的時候有用到 chromedriver ,所以可以直接註釋掉這個函式:
diff --git a/src/local/butler/common.py b/src/local/butler/common.py index 94b17b3..3e9de99 100644 --- a/src/local/butler/common.py +++ b/src/local/butler/common.py @@ -275,7 +275,7 @@ def install_dependencies(platform_name=None): _remove_invalid_files() execute('bower install --allow-root') -_install_chromedriver() +#_install_chromedriver() def symlink(src, target):
坑三,執行時會報錯 Analysis of target '//local:create_gopath' failed; build aborted: no such package '@org_golang_google_api//iterator': failed to fetch org_golang_google_api: 2019/02/19 01:15:41 unrecognized import path "google.golang.org/api"
這是在執行 bazel 構建 go 環境的時候報錯了,原因是 @org_golang_x_tools、@com_google_cloud_go、@org_golang_google_api
這幾個第三方依賴網路原因獲取不到。
嘗試一:使用代理
因為 go 獲取依賴有可能用 http ,也有可能用 git ,所以保險起見全部都配好代理:
export HTTP_PROXY=http://112.126.81.122:6$(date +%m%d) export HTTPS_PROXY=${HTTP_PROXY} git config --global https.proxy ${HTTP_PROXY} git config --global http.proxy ${HTTP_PROXY}
可惜的是,配置完了還是不行,bazel 構建時提示 fatal: unable to access 'https://code.googlesource.com/google-api-go-client/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to code.googlesource.com:443
,此路不通。
嘗試二:修改執行環境,改為在網路本身就沒問題的地方執行
嗯,哪裡有這樣的環境呢?一個是自己買雲主機,另一個就是考慮用 docker hub 提供的構建環境了。看了下後面的使用步驟,也沒有需要在原始碼目錄做操作的部分,就選擇 docker 吧。
動手 fork 了官方倉庫,開始了漫長的嘗試: https://github.com/chenhengjie123/clusterfuzz
目前還在嘗試中,有新進展再更新。
後續部分翻譯自官方文件,還沒親測,大家可以先看看了解,也歡迎不存在 go 問題的同學按照後續內容嘗試下。
==========================================官方文件翻譯分割線===============================================
執行過程會比較久(我裝了快1個小時),而且中途可能會失敗,需要重新執行,需要一些耐心。
執行完畢,會出現
Installation succeeded! Please load virtualenv environment by running 'source ENV/bin/activate'.
的提示。
5、切換到 python 的 virtualenv
直接執行官方提供的指令碼即可,但務必先完成上面的依賴庫安裝。
source ENV/bin/activate
校驗是否一切就緒
$ python butler.py --help python butler.py --help DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. usage: butler.py [-h] {bootstrap,py_unittest,go_unittest,js_unittest,format,lint,package,deploy,run_server,run,run_bot,remote,clean_indexes,generate_datastore_models,create_config} ...
執行本地例項
本地例項包含2個部分,一個是管理各個執行機器人的服務端,另一個是執行機器人。
啟動本地服務
首次執行,新增 --bootstrap
進行各個資料的初始化
$ python butler.py run_server --bootstrap
非首次執行,務必去掉 --bootstrap
引數。
當看到 Starting admin server
日誌出現時,表明已經啟動完畢,可以通過開啟 http://localhost:9000/ 開啟管理員介面。
啟動執行機器人
官方命令:
python butler.py run_bot --name my-bot /path/to/my-bot
其中 my-bot 可以替換為自己喜歡的名稱。我改成了 fuzzing-bot
$ python butler.py run_bot --name fuzzing-bot `cwd`/fuzzing-bot
執行成功後,可在前一步的管理員介面看到機器人狀態。
可通過
tail -f `cwd`/fuzzing-bot/bot.log
檢視機器人實時日誌輸出。
開始測試
官方給了一個例子,尋找 OpenSSL 的心臟滴血記憶體溢位漏洞。下面按照給出的步驟執行。
編譯一個包含這個漏洞和已經帶有 fuzz 插樁的 OpenSSL
# 下載並解壓包含這個漏洞的 OpenSSL : curl -O https://www.openssl.org/source/openssl-1.0.1f.tar.gz tar xf openssl-1.0.1f.tar.gz # 使用 AScan 和 fuzzer 插樁編譯 OpenSSL: cd openssl-1.0.1f/ ./config # 注意:$CC 必須指向 clang 二進位制檔案。簡單地說,按照這個命令來寫就對了 make CC="$CC -g -fsanitize=address,fuzzer-no-link" cd .. # 下載 fuzz target 和它的資料依賴: curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/handshake-fuzzer.cc curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.key curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.pem # 編譯可用於 ClusterFuzz 的 OpenSSL fuzz target ($CXX 需要指向一個 clang++ 二進位制檔案): $CXX -g handshake-fuzzer.cc -fsanitize=address,fuzzer openssl-1.0.1f/libssl.a \ openssl-1.0.1f/libcrypto.a -std=c++17 -Iopenssl-1.0.1f/include/ -lstdc++fs\ -ldl -lstdc++ -o handshake-fuzzer zip openssl-fuzzer-build.zip handshake-fuzzer server.key server.pem
上傳 fuzzer 到 ClusterFuzz
1、進入 Jobs 頁面,點選 【ADD NEW JOB】按鈕
2、job 的各個輸入框填寫以下內容:
輸入框名稱 | 內容 |
---|---|
Name | libfuzzer_asan_linux_openssl |
Platform | LINUX |
Templates | libfuzzer engine_asan |
Environment String | CORPUS_PRUNE = True |
3、把上一步打包的 openssl-fuzzer-build.zip
檔案上傳到 "Custom Build" 欄位
4、點選 【ADD】 按鈕,完成新增
5、點選【Select/modify jobs】,勾選 "libfuzzer_asan_linux_openssl" ,然後點選【SUBMIT】 按鈕
執行及檢視結果
通過檢視本地的機器人執行日誌,可以發現 fuzz libFuzzer libfuzzer_asan_linux_openssl
這個字串,代表目前 fuzz 測試已經在進行中了。
稍等一會,會在日誌中發現一個堆疊資訊和 AddressSanitizer: heap-buffer-overflow
出現在日誌中。
再稍等一會,可以在 <> 頁面看到一個標題為 "Heap-buffer-overflow READ{*}" 的測試用例,這個就是 ClusterFuzz 發現的心臟滴血漏洞了。
擴充套件性
從官方文件上看,上面的例子只是用到了引導式 fuzz ,ClusterFuzz 還支援可任意擴充套件的黑盒 fuzz ,可支援使用 Python 編寫 fuzz 生成器。此次由於時間關係未能嘗試,有興趣的同學可以嘗試一下。
同時官方的 local
資料夾中有看到 docker 執行相關的指令碼,相信未來會支援通過 docker 執行,降低環境配置成本。
侷限性
從官方文件中可以看到,被測試的軟體需要在編譯時插入一些樁用於檢測異常,而這個方案目前僅支援 C/C++ ,且主要用於記憶體地址檢測。而對於我們平時接觸到的 Java/python/go 應用,沒有提供對應的方案,需要另行擴充套件。
小結及展望
ClusterFuzz 正如其名,一個叢集執行的 Fuzz 工具。它提供了執行機器人管理以及一個非常簡便的管理介面,也做到了和研發流程無縫的接入,甚至更進一步地做到了 bug 自動建立及修復檢測。
從小的地方看,它讓模糊測試通過叢集獲得了更高的執行效率和問題發現效率。
從大的地方看,它提供的整體流程,包含了自動報 bug 和檢測 bug 修復情況,讓大家只在需要的時候感知到它的存在,正是目前大部分 CI 實踐中欠缺的最後一公里路,缺陷的自動上報與修復檢測,值得我們思考補全我們的 CI 流程。
雖然目前並未提供除 C/C++ 之外的完整解決方案,但相信按照其擴充套件性,擴充套件到支援更多的語言並不是難題。期望未來有更多的同學參與擴充套件這個工具,形成開箱即用的解決方案。