总的来说,对命令行进行模糊测试是一个发现软件问题的有效补充手段。
作者 |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://0CVE-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 01TOB-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=0Dockerfile
如果你想学习设置模糊测试工具的完整过程,并尝试对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 了