# Environment 官方文件有提供 step-by-step 的教學,但會踩到滿多坑的,記錄一下讓團隊內其他人未來可以快速建立環境。 因為會用到 GUI 介面,建議用 VirtualBox 或 VMWare 直接開一台 Virtual Machine 來設定。若有時間再嘗試用 Docker。 以下分別紀錄在 Local 環境和 Production 環境執行 ClusterFuzz 設定的步驟。 ## Local Setup Local 環境建置,我進行了兩個版本的測試,分別用 Docker 和 Virtual machine 建立。 根據官方文件,基本環境設置需要有 Google Cloud SDK, Python3.7, Go (未指明版本), Google Cloud SDK 在官方的 `install_deps.bash` 中就會安裝。 ### Docker 一開始為了方便快速建立環境,選擇在 docker 中安裝需要的套件並對 host mapping 會用到的 Port (9000),以便在 host 的瀏覽器頁面可以進到 Clusterfuzz 服務的頁面。Dockerfile 內容如下: ```dockerfile # Use ubuntu:20.04 to fulfill the requirements of GLIBC version (higher than 2.28) # ubuntu:20.04 -> GLIBC version 2.31 FROM ubuntu:20.04@sha256:3246518d9735254519e1b2ff35f95686e4a5011c90c85344c1f38df7bae9dd37 # Install RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && \ DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential wget curl git gnupg zlib1g-dev libffi-dev libsqlite3-dev libssl-dev lsb-core openssl apt-transport-https ca-certificates vim tmux sudo # Install Python 3.7.7 RUN cd /tmp/ && \ wget https://www.python.org/ftp/python/3.7.7/Python-3.7.7.tgz && \ tar xzf Python-3.7.7.tgz && \ cd Python-3.7.7 && \ ./configure --prefix=/opt/python/3.7.7/ --enable-loadable-sqlite-extensions --with-computed-gotos --with-system-ffi && \ make -j "$(nproc)" && \ make altinstall && \ rm /tmp/Python-3.7.7.tgz && \ ln -s /opt/python/3.7.7/bin/python3.7 /usr/local/bin/python3 # Install clusterfuzz dependencies RUN cd /root/ && \ git clone https://github.com/google/clusterfuzz.git && \ cd clusterfuzz && \ git pull && \ git checkout tags/v2.6.0 && \ ./local/install_deps.bash ``` 基於此 Dockerfile ,執行 `docker build -t clusterfuzz .` 建立 image。 這邊特別注意因為 Clusterfuzz 只支援在 x86_64 架構下執行,如果是 M1 晶片的 Mac 使用者需要特別指定 Ubuntu 為 amd64 版本的,否則預設會抓 arm64 的 Ubuntu,到後面安裝 dependencies 時會出問題。 建立好 image 之後,執行 `docker run -it —name <name> -p 9000:9000 -p 9001:9001 -p 9002:9002 /bin/bash` 進入容器環境。 進入容器後可以用 `pwd` 檢查一下當前的路徑,應該會在 `/root/clusterfuzz` 底下。開始執行 python script 之前,Clusterfuzz 要求必須要在 python 的虛擬環境下執行 script,而 Clusterfuzz 官方是使用 `pipenv` 這套工具,因此必須先執行: ```bash $ export SHELL=/bin/bash $ python3.8 -m pipenv shell ``` 應該會看到類似這樣的訊息: ```bash Launching subshell in virtual environment... . /root/.local/share/virtualenvs/clusterfuzz--qC05KhX/bin/activate ``` 就代表成功進到 Python 的虛擬環境了。接下來可以開始執行 clusterfuzz 的 script。開始前建議先開 `tmux` ,因為接下來有些 script (clusterfuzz 的 server) 會持續執行,要執行其他 script 需要多開終端機視窗。 ```bash # 第一次起 server 或要重新設定時必須加 --bootstrap $ python butler.py run_server --bootstrap # 後續起 server 加 --skip-install-deps $ python butler.py run_server --skip-install-deps ``` 等待 script 執行一陣子,看到下列訊息就代表服務有架設成功: ```bash Running: gunicorn -b :9000 main:app (cwd='src/appengine') | [2023-08-16 16:49:04 +0800] [12246] [INFO] Starting gunicorn 20.1.0 | [2023-08-16 16:49:04 +0800] [12246] [INFO] Listening at: http://0.0.0.0:9000 (12246) | [2023-08-16 16:49:04 +0800] [12246] [INFO] Using worker: sync | [2023-08-16 16:49:04 +0800] [12270] [INFO] Booting worker with pid: 12270 ``` 這時在本機端的瀏覽器打 `http://localhost:9000` 就可以看到 Clusterfuzz 的服務介面。 後續根據官方文件跑 OpenSSL Heartbleed 的範例在 Docker 內會遇到 `Failed to upload` 的錯誤,但因為後續還要跑 production setup,我就沒有繼續在 Docker 裡面除錯。要跑 OpenSSL Heartbleed 的範例參考後面 Virtual Machine 的說明。 **註**: 前面的步驟如果沒有先跑 `export SHELL=/bin/bash` 應該會 output 出如下的錯誤訊息: ```bash pipenv.vendor.shellingham._core.ShellDetectionFailure ``` 這裡我沒有去深究錯誤的原因,google 一陣子後沒有找到解決方法,丟 ChatGPT 才得到解決方法(指`export SHELL=/bin/bash`)。 ### Virtual Machine 這邊我用的是在 VirtualBox 上開一台 x86_64 架構的 Ubuntu20.04 的虛擬機,記得要用有 GUI 介面的 Desktop 版,不要用到 Server 版 (不小心用到的話,也可以後續透過 `sudo apt install ubuntu-desktop` )。 - Check version by: ```bash $ uname -m # or $ cat /etc/os-release ``` 確認是 x86_64 架構的機器即可,因為 clusterfuzz 不支援 arm 架構。 指令跟 Docker 相同,但部分指令需要改以 `sudo` 方式執行 (因為 Docker 在建立階段本身就是以管理員身分執行,因此不用額外加 sudo),執行指令如下: ```bash $ sudo apt-get update -y && \ sudo apt-get install -y build-essential wget curl git gnupg zlib1g-dev libffi-dev libsqlite3-dev libssl-dev lsb-core openssl apt-transport-https ca-certificates vim sudo $ cd /tmp/ && \ wget https://www.python.org/ftp/python/3.7.7/Python-3.7.7.tgz && \ tar xzf Python-3.7.7.tgz && \ cd Python-3.7.7 && \ sudo ./configure --prefix=/opt/python/3.7.7/ --enable-loadable-sqlite-extensions --with-computed-gotos --with-system-ffi && \ sudo make -j "$(nproc)" && \ sudo make altinstall && \ sudo rm /tmp/Python-3.7.7.tgz && \ sudo ln -s /opt/python/3.7.7/bin/python3.7 /usr/local/bin/python3 $ cd /root/ && \ git clone https://github.com/google/clusterfuzz.git && \ cd clusterfuzz && \ git pull && \ git checkout tags/v2.6.0 && \ sudo ./local/install_deps.bash ``` 到這邊所有的套件都已安裝完成,接下來一樣建議先開 `tmux` 並開虛擬環境執行 server: ```bash $ tmux $ python3.8 -m pipenv shell $ python butler.py run_server --bootstrap ``` 等待服務成功架設,開另一個終端機視窗,接下來要執行 bot 相關的 script。 ```bash $ python butler.py run_bot --name [bot-name] /path/to/bot-name # Example $ python butler.py run_bot --name my-bot /home/username/my-bot ``` 這個 script 一樣會持續執行,並在你指定的路徑下新增你所命名的資料夾,以上面這個例子來說,會在 `/home/username` 下新增一個 `/my-bot` 資料夾,裡面存放 bot 相關的資訊。這時在 <http://localhost:9000> 中的 Bots 頁面應該可以看到一個 my-bot 機器人。 這時再多開一個終端機視窗,接下來要看 bot 的 log。 ```bash $ cd /path/to/bot-name/clusterfuzz/bot/log $ tail -f bot.log ``` 這時候因為還沒有任何的 job,所以 bot 不會執行 task,因此會看到如下的錯誤訊息: ```bash ... ... 2023-08-16 18:06:01,795 - run_bot - ERROR - Failed to get any fuzzing tasks. This should not happen. ``` 因此接下來就實際照官方文件來跑用 Clusterfuzz 找出 OpenSSL 這個以 C++ 撰寫的套件中的漏洞的範例吧 ! ### Heartbleed example 在跑這個範例之前,必須要先下載 clang 編譯器,根據 llvm 官方網站,執行: ```bash $ bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ``` 這個 script 會在 `/usr/bin` 下安裝 clang 相關執行檔 ( `clang-17, clang++-17` ),`17` 為版本編號。接著必須分別把這兩個 clang 相關執行檔設為環境變數: ```bash # This is just an example, the path depends on where you installed $ export CC=/usr/bin/clang-17 $ export CXX=/usr/bin/clang++-17 ``` 接著照官方文件說明執行: ```bash # Download and unpack a vulnerable version of OpenSSL: curl -O https://ftp.openssl.org/source/old/1.0.1/openssl-1.0.1f.tar.gz tar xf openssl-1.0.1f.tar.gz # Build OpenSSL with ASan and fuzzer instrumentation: cd openssl-1.0.1f/ ./config # $CC must be pointing to clang binary, see the "compiler section" link above. make CC="$CC -g -fsanitize=address,fuzzer-no-link" cd .. # Download the fuzz target and its data dependencies: 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 # Build OpenSSL fuzz target for ClusterFuzz ($CXX points to clang++ binary): $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 ``` 過程主要是在編譯有問題的 OpenSSL 套件,接著就可以把這個套件實際上傳到 ClusterFuzz 的介面進行測試。 在 ClusterFuzz 的介面: 1. 前往 Jobs 頁面 2. 找到 “Add New Job“ 的表格,填入下列內容: - **“libfuzzer_asan_linux_openssl”** for the “Name”. - **“LINUX”** for the “Platform”. - **“libFuzzer”** for the “Select/modify fuzzers”. - **“libfuzzer”** and **“engine_asan”** for the “Templates”. - `CORPUS_PRUNE = True` for the “Environment String”. 3. 在 Custom Build 欄位選擇剛剛壓縮的 `openssl-fuzzer-build.zip` 檔案上傳 4. 點選 “Add“,完成新增 Job 的工作。 稍微等待幾分鐘後,可以到 Bots 頁面觀看前述步驟新增的 `my-bot`,這時應該會顯示 `fuzz libfuzzer libfuzzer_asan_linux_openssl` 代表已經找到漏洞。回到 `/my-bot/clusterfuzz/bot/logs` 中看 `bot.log` 這個檔案的 outpup: ```bash $ cat bot.log ... ... SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/cdxvy30/my-bot/clusterfuzz/bot/builds/libfuzzer_asan_linux_openssl/custom/handshake-fuzzer+0x281079) (BuildId: 425751f4a59f9763ff45351813695bb53fb9949c) Shadow bytes around the buggy address: 0x529000009480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x529000009500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x529000009580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x529000009600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x529000009680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x529000009700: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa 0x529000009780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x529000009800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x529000009880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x529000009900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x529000009980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==14754==ABORTING MS: 1 ShuffleBytes-; base unit: 0ba0c1311882a3a494570d4b26201ca3c5b2e675 0x18,0x3,0x0,0x0,0x1,0x1,0x0,0x3,0x0,0xa7,0xa7,0x3,0x1,0x0,0x0,0x1,0x3,0x2,0x0,0x3,0x7a,0xff,0x0, \030\003\000\000\001\001\000\003\000\247\247\003\001\000\000\001\003\002\000\003z\377\000 artifact_prefix='/home/cdxvy30/my-bot/clusterfuzz/bot/inputs/fuzzer-testcases/'; Test unit written to /home/cdxvy30/my-bot/clusterfuzz/bot/inputs/fuzzer-testcases/crash-0970e17d686ca8e34447447f3864c04148e25a58 Base64: GAMAAAEBAAMAp6cDAQAAAQMCAAN6/wA= stat::number_of_executed_units: 18700 stat::average_exec_per_sec: 0 stat::new_units_added: 65 stat::slowest_unit_time_sec: 0 stat::peak_rss_mb: 137 ... ... ``` 代表已經成功找出 OpenSSL 這個函式庫中的漏洞。 ### Integrate with Golang 因在 Go Team 的專案裡面,要測試的對象是以 Golang 撰寫,所以必須整合上傳 Golang 的功能。由於 ClusterFuzz 吃的是 libFuzzer 介面的檔案,而參考這個 [issue](https://github.com/google/clusterfuzz/issues/860) 中提到有專門編譯 Golang 成 libFuzzer 介面的[專案](https://github.com/dvyukov/go-fuzz),看起來可用於整合 Golang 檔案上傳至 ClusterFuzz。 ## Production Setup 設定 Production 環境會用到 Google Cloud Platform 的服務,因此需要先準備一個還有免費額度的 GCP 帳號。另外因為必須要通過 Google OAuth 驗證,而在 Docker 中沒有 GUI 介面,無法透過瀏覽器進行 Google 的 OAuth 驗證,因此這部分目前只能用 Virtual machine 進行設定。 ### Virtual Machine 接續 Local Setup 的步驟,接下來需要到 Google Cloud Platform 上新增一些設定。這部分[官方文件](https://google.github.io/clusterfuzz/production-setup/clusterfuzz/)寫的滿詳細,主要跟著指示依序在 GCP, Firebase 上做設定並 export 環境變數: ```bash # Login to your Google account $ gcloud auth application-default login $ gcloud auth login # Verify that your project is successfully created $ gcloud projects describe <your project id> # Export the project id in environment for later use $ export CLOUD_PROJECT_ID=<your project id> # Enable Firebase # Copy the apiKey value, and export it $ export FIREBASE_API_KEY=<your api key> # Create OAuth credentials, and export it $ export CLIENT_SECRETS_PATH=/path/to/your/client_secrets.json # Create config directory mkdir /path/to/yourconfig # Any EMPTY directory outside the ClusterFuzz source repository. export CONFIG_DIR=/path/to/yourconfig ``` 最後執行: ```bash $ python butler.py create_config --oauth-client-secrets-path=$CLIENT_SECRETS_PATH \ --firebase-api-key=$FIREBASE_API_KEY --project-id=$CLOUD_PROJECT_ID $CONFIG_DIR ``` 注意執行這個 script 後會跳出瀏覽器視窗要做 OAuth 驗證,記得選前面設定的 Google 帳戶即可。 ### Google Cloud setup 特別注意根據官方 script 跑 config 會遇到 `clouddebugger.googleapis.com` 這個功能無法啟用的情況,需要手動進入 config 的檔案把該服務註解掉,因為該服務已經在 2023/05/29 終止服務,而在 script 裡面會一直去要求 enable 該服務導致出錯。 ### Python version check 有可能會遇到 Python version check 的錯誤,如果遇到的話,跟著錯誤訊息中提到的檔案,把對應的 version check 註解掉即可。 ### Verification 完成上述步驟之後,可以到 `https://<your project id>.appspot.com` 確認是否可以看到 ClusterFuzz 的頁面。如果可以的話就代表服務有架設起來了,也可以到個人的 Google Cloud Console 去看 App Engine 等頁面,應該會看到一些相關資訊。 ### 小結 目前服務雖然有架設起來,但是 bot 沒有成功設置,目前還在 debug 中。目前 Production Setup 過程遇到的問題如下: #### grpc - 實際進入執行 ClusterFuzz 任務的 bot (linux-vm) 上看,clusterfuzz 的部署行為大致是: - 開機後執行一些前置作業包括設定 username、環境變數、swappiness、iptables 的 health check等 - 實際執行 clusterfuzz.service 這個服務,應該是透過 `systemctl start` - 這裡會把官方的 image (gcr.io/clusterfuzz-images/base) pull 下來 - `docker run` 把 host 剛剛從 `gs://deployment.omega-dahlia-395309.appspot.com` 上下載下來的 `linux-3.zip` unzip 掛到容器內部然後執行初始化腳本 (`/docker/base/*.sh`) - 跑到 `grpc` 相關的 `import` 時出錯 - 嘗試從 `/base/Dockerfile` 除錯,重新 build image,主要想重新下載 grpc library 到它指定的 PYTHONPATH。 - 解決: 把 dockerfile 中的 RUN_CMD python3.7 改成 3.8,重新設定後不用改。 - 其他: `init.yaml` 有改的話,version 數字要自行加 1,否則 VM 只會重啟不會重新。initialize - 參考: - https://github.com/google/clusterfuzz/issues/253 - https://github.com/google/clusterfuzz/issues/252 #### download_from_string is broken - `grpc` 的問題修正之後,GCP 上的 VM 可正常運作,但在上傳要進行模糊測試的檔案到 GCS 之後,bot 有顯示在執行 job,但其實進到 VM 的 log 裡面看,沒有看到顯示在測試的 log。這裡我一開始因為看到這個 [issue](https://github.com/google/clusterfuzz/issues/3257),就想著要去改原始碼,但其實這個方向不對,因為我測試是用 2023 年 2 月 release 的 `2.6.0` 版,而 issue 的原因是 2023 年 7 月之後 `google-cloud-sdk` 有個 method deprecated ,所以應該不是這個問題造成錯誤。而後續尚未針對此問題進行 debug。 ## Other Tracking Issues 1. [Replace download_as_string with download_as_bytes](https://github.com/google/clusterfuzz/issues/3257) 2. [Fix prerequisites documentation](https://github.com/google/clusterfuzz/issues/3289) 3. [Upgrade Python](https://github.com/google/clusterfuzz/issues/3218) 4. [Upload error when adding a job](https://github.com/google/clusterfuzz/issues/2241) ## My Current ClusterFuzz Codebase and Configuration 1. https://github.com/cdxvy30/clusterfuzz 2. https://github.com/cdxvy30/clusterfuzz-config