cURL安全审计:从玩笑到重大漏洞发现的技术解析
cURL审计:一个玩笑如何导致重大发现
项目背景
2022年秋季,Trail of Bits对cURL进行了安全审计。cURL是一个广泛使用的命令行工具,用于在服务器之间传输数据并支持多种协议。该项目恰逢Trail of Bits的创客周,这意味着我们拥有比平时更多的人力资源,使我们能够采用非标准方法进行审计。
在讨论应用程序的威胁模型时,我们的一位团队成员开玩笑地问:"我们试过curl AAAAAAAAAA...了吗?"虽然这个评论是开玩笑的,但它激发了一个想法:我们应该对cURL的命令行界面(CLI)进行模糊测试。一旦我们这样做了,模糊测试器很快就发现了内存损坏漏洞,特别是释放后使用问题、双释放问题和内存泄漏。由于这些漏洞存在于libcurl(一个cURL开发库)中,它们有可能影响许多使用libcurl的软件应用程序。
发现的漏洞
- 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 GitHub仓库中开发。在查阅curl-fuzzer仓库以了解cURL模糊测试的当前状态时,我注意到cURL的命令行界面(CLI)参数没有被模糊测试。考虑到这一点,我决定专注于测试cURL对参数的处理。
我使用AFL++模糊测试器(AFL的一个分支)为cURL的CLI生成大量随机输入数据。我在链接时使用无冲突检测和AddressSanitizer编译cURL,然后分析可能指示错误的崩溃。
模糊测试argv
当我开始尝试模糊测试cURL时,我寻找了一种使用AFL来模糊测试其参数解析的方法。我的搜索引导我找到了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数组中。数组以两个NULL字符终止,任何空参数都被编码为单独的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_INIT_SET0的调用,如下所示:
// curl/src/tool_main.c
#include "../../AFLplusplus/utils/argv_fuzzing/argv-fuzz-inl.h"
int main(int argc, char *argv[])
{
AFL_INIT_ARGV();
// ... 其余代码
}
准备字典
模糊测试字典文件指定了模糊测试引擎在测试期间应关注的数据元素。模糊测试引擎调整其变异策略,以便处理字典中的标记。在cURL模糊测试的情况下,模糊测试字典可以帮助afl-fuzz更有效地生成包含选项(以一个或两个破折号开头)的有效测试用例。
为了模糊测试cURL,我使用了afl-clang-lto编译器的自动字典功能,该功能在编译目标二进制文件期间自动生成字典。该字典在启动时传输到afl-fuzz,提高了其覆盖率。我还基于cURL手册页准备了一个自定义字典,并通过-x参数将其传递给afl-fuzz。我使用以下Bash命令准备字典:
man curl | grep -oP '^\s*(--|-)\K\S+' | sed 's/[,.]$//' | sed 's/^/"&/; s/$/&"/' | sort -u > curl.dict
为cURL连接设置服务
最初,我的重点仅仅是CLI模糊测试。但是,我必须考虑到模糊测试器生成的每个有效cURL命令都可能导致连接到远程服务。为了避免连接到这些服务但保持测试负责处理连接的代码的能力,我使用netcat工具作为远程服务的模拟。
首先,我配置我的机器将传出流量重定向到netcat的监听端口。我使用以下命令在后台运行netcat:
netcat -l 80 -k -w 0 &
参数指示服务应在端口80(-l 80)上侦听传入连接,在当前连接关闭后继续侦听其他连接(-k),并在建立连接后立即终止连接(-w 0)。
cURL预计会使用各种主机名、IP地址和端口连接到服务。我需要将它们转发到一个地方:先前创建的TCP端口80。
为了将所有传出的TCP数据包重定向到本地环回地址(127.0.0.1)上的端口80,我使用了以下iptables规则:
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 80
该命令在iptables的网络地址转换表中添加一个新条目。-p选项指定协议(在本例中为TCP),-j选项指定规则的目标(在本例中为REDIRECT)。--to-port选项指定数据包将被重定向到的端口(在本例中为80)。
为了确保所有域名都将解析为IP地址127.0.0.1,我使用了以下iptables规则:
iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1
此规则在NAT表中添加一个新条目,指定协议(-p)为UDP,目标端口(--dport)为53(DNS的默认端口),目标(-j)为目标NAT。--to-destination选项指定数据包将被重定向到的地址(在本例中为127.0.0.1)。
上述设置确保每个cURL连接都定向到地址127.0.0.1:80。
结果分析
模糊测试过程在具有Intel Xeon Platinum 8280 CPU @ 2.70GHz的32核机器上运行了一个月。在此期间发现了以下错误,其中大部分在模糊测试的前几个小时内发现:
CVE-2022-42915(使用HTTP代理与特定协议时的双释放)
将cURL与代理连接以及dict、gopher、LDAP或telnet协议一起使用会由于错误/清理处理中的缺陷而触发双释放漏洞。此问题在cURL 7.86.0中修复。
要重现该错误,请使用以下命令:
curl -x 0:80 dict://0
CVE-2022-43552(当HTTP代理拒绝隧道SMB/TELNET协议时的释放后使用)
cURL可以通过HTTP代理虚拟隧道支持的协议。如果HTTP代理阻止SMB或TELNET协议,cURL可能会在其传输关闭代码中使用已被释放的结构。此问题在cURL 7.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(使用并行选项和序列时的释放后使用)
通过使用带有并行选项(-Z)、不匹配的括号和创建51个主机的两个连续序列的cURL,可以触发释放后使用漏洞。cURL为错误缓冲区分配内存块,默认允许最多50个传输。在负责处理错误的函数中,当连接失败时,错误被复制到相应的错误缓冲区,然后释放内存。对于最后一个(51)序列,分配内存缓冲区,释放,并将错误复制到先前释放的内存缓冲区。此问题在cURL 7.86.0中修复。
要重现该错误,请使用以下命令:
curl 0 -Z [q-u][u-~] }
TOB-CURL-11(未使用的内存块未被释放,导致内存泄漏)
cURL分配的内存块在不再需要时未被释放,导致内存泄漏。此问题在cURL 7.87.0中修复。
要重现该错误,请使用以下命令:
curl 0 -Z 0 -Tz 0
curl 00 --cu 00
curl --proto =0 --proto =0
Dockerfile
如果你想了解设置模糊测试工具的全过程并立即开始模糊测试cURL的CLI参数,我们为你准备了一个Dockerfile:
# syntax=docker/dockerfile:1
FROM aflplusplus/aflplusplus:4.05c
RUN apt-get update && apt-get install -y libssl-dev netcat iptables groff
# 克隆curl仓库
RUN git clone https://github.com/curl/curl.git && cd curl && git checkout 2ca0530a4d4bd1e1ccb9c876e954d8dc9a87da4a
# 应用补丁以使用afl++ argv模糊测试功能
COPY <<-EOT /AFLplusplus/curl/curl_argv_fuzz.patch
diff --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 loaded
modules. This is purposely pre-init. */
EOT
# 应用补丁以使用afl++ argv模糊测试功能
RUN cd curl && git apply curl_argv_fuzz.patch
# 使用无冲突检测和ASAN编译curl
RUN cd curl && \
autoreconf -i && \
CC="afl-clang-lto" CFLAGS="-fsanitize=address -g" ./configure --with-openssl --disable-shared && \
make -j $(nproc) && \
make install
# 下载字典
RUN wget https://gist.githubusercontent.com/ahpaleus/f94eca6b29ca8824cf6e5a160379612b/raw/3de91b2dfc5ddd8b4b2357b0eb7fbcdc257384c4/curl.dict
COPY <<-EOT script.sh
#!/bin/bash
# 在后台运行netcat监听器在tcp端口80上
netcat -l 80 -k -w 0 &
# 准备iptables条目
iptables-legacy -t nat -A OUTPUT -p tcp -j REDIRECT --to-port 80
iptables-legacy -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1
# 准备模糊测试目录
mkdir fuzz &&
cd fuzz &&
mkdir in out &&
echo -ne 'curl\x00http://127.0.0.1:80' > in/example_command.txt &&
# 运行afl++模糊测试器
afl-fuzz -x /AFLplusplus/curl.dict -i in/ -o out/ -- curl
EOT
RUN chmod +x ./script.sh
ENTRYPOINT ["./script.sh"]
使用以下命令运行此文件:
docker buildx build -t curl_fuzz .
docker run --rm -it --cap-add=NET_ADMIN curl_fuzz
总结
总之,我们的方法表明,模糊测试CLI可以成为识别软件漏洞的有效补充技术。尽管最初持怀疑态度,但我们的结果产生了宝贵的见解。我们相信这提高了基于CLI的工具的安全性,即使OSS-Fuzz已经使用了多年。
在cURL清理过程中找到基于堆的内存损坏漏洞是可能的。但是,释放后使用漏洞可能无法被利用,除非释放的数据以适当的方式使用并且数据内容受控。双释放漏洞需要进一步分配类似大小的内存块并控制存储的数据。此外,由于漏洞在libcurl中,它可能以各种方式影响许多使用libcurl的不同软件应用程序,例如发送多个请求或在单个进程内设置和清理库资源。
还值得注意的是,尽管CLI利用的攻击面相对有限,但如果受影响的工具是SUID二进制文件,利用可能导致权限提升(参见CVE-2021-3156:sudo中的基于堆的缓冲区溢出)。
为了将来提高类似工具的模糊测试效率,我们通过合并持久模糊测试模式扩展了AFL++中的argv_fuzz功能。在此处了解更多信息。
最后,我们的cURL审计报告是公开的。查看审计报告和威胁模型。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码