afl(關于afl的基本詳情介紹)

總的來說,對命令行進行模糊測試是一個發現軟件問題的有效補充手段。

作者 |Maciej Domanski譯者|彎月

出品 | CSDN(ID:CSDNnews)

2022 年秋天,我們公司決定審核Curl,這是一款使用非常廣泛的命令行程序,可在服務器之間傳輸數據,而且還支持各種協議。當時恰逢公司的創作周,我們有比平時更多的人手,因此審核可以采用非標準的方法。

在討論應用程序的威脅模型時,一位團隊成員開玩笑說:我們試過CurlAAAAAAAAAA……了嗎?盡管這是一句話玩笑話,但我們因此萌生了一個想法:我們應該對Curl的命令行界面(CLI)進行模糊測試。

很快,我們就發現了會導致內存損壞的錯誤,具體來說包括釋放后使用問題、雙重釋放問題以及內存泄漏問題。由于這些是Curl開發庫 libcurl 中的 bug,因此可能會影響到許多使用了該庫的軟件應用程序。

本文將介紹我們發現如下漏洞的經過:

CVE-2022-42915:使用特定協議的 HTTP 代理時的雙重釋放問題。已在 Curl 7.86.0 中得到修復。

CVE-2022-43552:當 HTTP 代理拒絕隧道 SMB/TELNET 協議時的釋放后使用問題。在 Curl 7.87.0 中得到修復。

TOB-CURL-10:使用并行選項和序列時的釋放后使用問題。已在 Curl 7.86.0 中得到修復。

TOB-CURL-11:未使用的內存塊沒有得到釋放,從而引發內存泄漏。已在 Curl 7.87.0 中得到修復。

使用 cURL

一直以來,Curl的模糊測試由 OSS-Fuzz 項目負責,相應的工具由 curl-fuzzer 開發。在檢查Curl模糊測試的當前狀態時,我注意到Curl的命令行界面參數沒有經過模糊測試。于是,我決定專門測試一下Curl對參數的處理。我使用的工具是 AFL++(AFL 的一個分支),生成了大量的隨機輸入數據。我首先使用 AddressSanitizer 編譯了Curl,然后分析了可能有潛在 bug 的崩潰。

Curl通過命令行參數獲取選項。由于Curl遵循 C89 標準,因此程序的 main() 函數可以不帶參數或帶兩個參數(argc 和 argv)。參數 argc 表示傳遞給程序的命令行參數的數量(包括程序的名稱);參數 argv 是一個指針數組,指向命令行傳遞給程序的參數。

該標準還規定,在托管環境中,main() 函數還可以接受第三個參數:char *envp[]。這個參數指向一個以 結尾的指針數組,每個指針指向一個包含有關程序環境信息的字符串。

這三個參數的名稱任意,因為它們只在所處的函數中有效。

Curl的 main() 函數位于 curl/src/tool_main.c 文件中,它將命令行參數傳遞給函數 operate(),該函數會解析這些參數,并設置Curl的全局配置。然后Curl使用這些全局配置來執行操作。

argv 的模糊測試

在嘗試對Curl進行模糊測試時,我四處尋找方法,使用 AFL 對Curl的參數解析進行模糊測試。我找到了 AFL 的創建者 Michal Zalewski 的一段話:

AFL 不支持 argv 模糊測試,因為老實說這種測試在實踐中并沒有太大用處。experimental/argv_fuzzing/ 中有一個示例,展示了如何進行這種測試。

我查看了 AFL 的這個實驗性功能以及 AFL++ 中同等的功能。argv 模糊測試功能可以測試從 CLI 傳遞給程序的參數(注意不是標準輸入)。當你想在模糊測試中覆蓋一個庫的多個 API 時,就可以對使用這個庫的工具的參數進行模糊測試,而無需為每個 API 編寫多個模糊測試。

AFL++ 的 argvfuzz

argvfuzz 的頭文件 argv-fuzz-inl.h 定義了兩個宏,從模糊測試工具獲得輸入,然后設置 argv 和 argc:

AFL_INIT_ARGV() 宏使用命令行傳遞的參數初始化 argv 數組。然后,從標準輸入讀取參數,將其放到 argv 數組中。該數組以兩個 字符結尾,空參數編碼成單獨的0x02字符。

AFL_INIT_SET0(_p)宏與AFL_INIT_ARGV()相似,但會同時將 argv 數組的第一個元素設置為傳遞給它的值。如果你想在 argv 數組中保留程序的名稱,這個宏就非常有用。

兩個宏都依賴于 afl_init_argv() 函數,該函數負責從標準輸入讀取一條命令行(通過 unistd.h 頭文件中的 read() 函數),然后將其分割成多個參數。然后再將得到的字符串數組保存在一個靜態緩沖區中,并返回指向該緩沖區的指針。它還會將 argc 參數指向的值設置為讀入的參數個數。

為了使用 argv-fuzz 功能,你需要在 main() 函數的文件中包含 argv-fuzz-inl.h 頭文件,在 main() 開頭調用 AFL_INIT_ARGV 或 AFL_IJNIT_SET0,如下所示:

curl/src/tool_main.c

準備字典

模糊字典文件用于指定模糊測試引擎在測試過程中需要使用的數據。模糊測試引擎會調整其策略,以處理字典中的詞。在Curl模糊測試中,模糊字典可以幫助 afl-fuzz 更有效地生成正確的測試用例(即包含以一個或兩個橫線開頭的選項的用例)。

為了對Curl進行模糊測試,我使用了編譯器 afl-clang-lto 的自動字典功能,它可以在編譯目標可執行文件的過程中自動生成字典文件。該字典文件在啟動時傳遞給 afl-fuzz 程序,以提高其覆蓋率。我還根據Curl的手冊頁面準備了一個自定義字典,通過 -x 參數傳遞給 afl-fuzz 程序。我使用了下面的 Bash 命令來準備字典:

$ man curl| grep -oP ^\s*(--|-)\K\S+| seds/[,.]$//| seds/^/"&/; s/$/&"/ | sort -u > curl.dict

為Curl連接設置服務

我一開始的目標只是給命令行做模糊測試。但我仍然需要考慮模糊測試工具生成的每個有效的Curl命令是否會真的連接到某個遠程服務。為了避免連接到真正的服務,同時保證能夠測試到負責連接的代碼,我使用 netat 工具生成了一個模擬的遠程服務。首先,我修改了機器配置,將所有出網流量重定向到 netcat 監聽的端口。

我用下述命令在后臺運行 netcat:

$netcat-l80-k-w0&

上述參數表明,該服務會在80端口上監聽進入的連接(-l 80),在當前連接結束后繼續監聽(-k),在連接建立后立即斷開(-w 0)。

Curl會用各種不同的主機名、IP 地址和端口連接服務。我需要將所有連接都轉到前面創建的 TCP 80端口上。

為了將所有出網的 TCP 包都重定向到本地環回地址的80端口,我使用了如下iptables 規則:

$iptables -t nat -A OUTPUT -p tcp -j REDIRECT--to-port 80

該命令給 iptables 中的 NAT 表添加了一條規則。-p 選項指定協議為 TCP,-j選項指定了規則的目標為 REDIRECT。--to-port 指定包將被重定向到80端口。

為了保證所有域名都解析到127.0.0.1,我使用了如下 iptables 規則:

$iptables-tnat-AOUTPUT-pudp--dport53-jDNAT--to-destination127.0.0.1

該規則給 NAT 表添加了一項,協議(-p)為 UDP,目標端口(--dport)為53( DNS 的默認端口),目標(-j)為目標 NAT。--to-destination 選項指定了包將被重定向到的地址(這里為127.0.0.1)。

上述設置可以保證所有Curl連接都重定向到127.0.0.1:80。

結果分析

模糊測試在一臺32核的 Intel Xeon Platnum 8280 CPU @ 2.70GHz 的機器上運行了一個月。運行期間發現了下面的問題,大多數都是在模糊測試開始后幾個小時內發現的。

CVE-2022-42915(使用特定協議的 HTTP 代理時的雙重釋放問題)

錯誤的處理和清理出了問題,導致Curl在使用代理和 dict、gopher、LDAP、telnet 協議時會觸發一個雙重釋放問題。該問題在Curl7.86.0中得到了修復。

你可以通過如下命令重現這個問題:

$ curl -x0:80dict://0

CVE-2022-43552( HTTP 代理拒絕隧道 SMB/TELNET 協議時導致釋放后使用問題)

Curl幾乎可以通過 HTTP 協議來隧道任何支持的協議。如果 HTTP 代理拒絕SMB 或 TELNET 協議,Curl可能會使用一個結構,而該結構已經在傳輸關閉的處理代碼中釋放。該問題在Curl7.87.0中得到了修復。

你可以通過如下命令重現這個問題:

$ curl 0 -x0:80 telnet:/[j-u][j-u]//0 -m 01$ curl 0 -x0:80 smb:/[j-u][j-u]//0 -m 01

TOB-CURL-10(當使用并行選項和特定字符序列時的釋放后使用問題)

當Curl使用并行選項(-z)時,如果遇到不匹配的大括號以及兩個連續的、能創建51個主機的字符序列時,就會觸發一個釋放后使用問題。Curl給錯誤緩沖區分配了內存,默認允許同時進行50個傳輸。而在負責處理錯誤的函數中,如果連接出錯,錯誤會復制到適當的錯誤緩沖區中,然后其內存被釋放。而最后一個(第51個)序列會被分配一片緩沖區、釋放,然后錯誤被復制到一個已經釋放的緩沖區中。該問題在Curl7.86.0中得到了修復。

你可以通過如下命令重現這個問題:

$ curl 0 -Z [q-u][u-~] }

TOB-CURL-11(未使用的內存塊未釋放,導致內存泄漏)

Curl分配的一些內存在不需要時沒有釋放,導致內存泄漏。該問題在Curl7.87.0中得到了修復。

你可以通過如下命令重現這個問題:

$curl0-Z0-Tz0$curl00--cu00$curl--proto=0--proto=0

Dockerfile

如果你想學習設置模糊測試工具的完整過程,并嘗試對Curl的命令行參數進行模糊測試,可以使用以下 Dockerfile:

syntax=docker/dockerfile:1FROM aflplusplus/aflplusplus:4.05cRUN apt-getupdate&& apt-getinstall-y libssl-dev netcat iptables groffClone a curl repositoryRUN gitclonehttps://github.com/curl/curl.git && cd curl && git checkout2ca0530a4d4bd1e1ccb9c876e954d8dc9a87da4aApply a patch to use afl++ argv fuzzing featureCOPY <<-EOT /AFLplusplus/curl/curl_argv_fuzz.patchdiff--git a/src/tool_main.c b/src/tool_main.c--- a/src/tool_main.c+++ b/src/tool_main.c@@-54,6+54,7@@include "tool_vms.h"include "tool_main.h"include "tool_libinfo.h"+include "../../AFLplusplus/utils/argv_fuzzing/argv-fuzz-inl.h"/** This is low-level hard-hacking memory leak tracking and similar. Using@@ -246,6 +247,8 @@ int main(int argc, char *argv[])struct GlobalConfig global;memset(&global, 0, sizeof(global));+ AFL_INIT_ARGV();+ifdef WIN32/* Undocumented diagnostic option to list the full paths of all loadedmodules. This is purposely pre-init. */EOTApply a patch to use afl++ argv fuzzing featureRUN cd curl && gitapplycurl_argv_fuzz.patchCompile a curl using collision-free instrumentation at link time and ASANRUN cd curl && \autoreconf -i && \CC="afl-clang-lto"CFLAGS="-fsanitize=address -g"./configure--with-openssl --disable-shared && \make -j $(nproc) && \makeinstallDownload a dictionaryRUN wgethttps://gist.githubusercontent.com/ahpaleus/f94eca6b29ca8824cf6e5a160379612b/raw/3de91b2dfc5ddd8b4b2357b0eb7fbcdc257384c4/curl.dictCOPY <<-EOT script.sh!/bin/bashRunning a netcat listener on port tcp port 80 in the backgroundnetcat -l80-k -w0&Prepare iptables entriesiptables-legacy -t nat -AOUTPUT-p tcp -j REDIRECT--to-port 80iptables-legacy -t nat -AOUTPUT-p udp--dport 53 -j DNAT --to-destination 127.0.0.1Prepare fuzzing directoriesmkdir fuzz &&cd fuzz &&mkdirinout&&echo -necurl\x00http://127.0.0.1:80>in/example_command.txt &&Run afl++ fuzzerafl-fuzz -x /AFLplusplus/curl.dict -iin/ -oout/-- curlEOTRUN chmod +x ./script.shENTRYPOINT ["./script.sh"]

運行該文件的命令如下:

$docker buildx build -t curl_fuzz .$docker run --rm -it --cap-add=NET_ADMIN curl_fuzz

總結

總的來說,我們的方法證明了對命令行進行模糊測試是一個發現軟件問題的有效補充手段。盡管最初人們懷疑其有效性,但我們的結果確實提供了有價值的見解。我們相信,這種方法可以增強命令行工具的安全性,盡管 OSS-Fuzz 已被使用多年。

我們有可能在Curl的清理過程中找到一個與堆有關的內存破壞問題。但是,除非被釋放的數據被合理使用,且能夠控制數據的內容,否則沒辦法觸發使用后釋放問題。而雙重釋放問題需要不斷分配大小相近的內存,而且能夠控制數據的內容。此外,由于問題出現在 libcurl 中,它們可能會影響許多使用了 libcurl 的軟件程序,例如發送多個請求的程序,或在同一個進程中設置并清理庫資源的程序。

另一點值得一提的事,盡管命令行的攻擊面很有限,但如果受到影響的工具是一個 SUID 的可執行文件,這些問題就可能引起權限提升(見 CVE-2021-3156:sudo 中的堆緩沖區溢出)。

為了提高模糊測試的效率,我們擴展了 AFL++ 中的 argv_fuzz 功能,加入了一個持久模糊模式。詳情請見這里(https://github.com/AFLplusplus/AFLplusplus/pull/1607)。

最后,我們的Curl審計報告已公開,請參見審計報告(https://github.com/trailofbits/publications/blob/master/reviews/2022-12-curl-securityreview.pdf)及威脅模型(https://github.com/trailofbits/publications/blob/master/reviews/2022-12-curl-threatmodel.pdf)。

?華為起訴小米專利侵權,國家知識產權局已受理;iPhone 等設備電池正式漲價;FFmpeg 6.0 發布|極客頭條

?萬億模型訓練需 1.7TB 存儲,騰訊混元如何突破 GPU 極限?

?沒有這些,別妄談做 ChatGPT 了

轉載注明出處:華峰博客網

內容版權聲明:除非注明,否則皆為本站原創文章。