3453c67597993d5b4d1fef77c001f073.gif

总的来说,对命令行进行模糊测试是一个发现软件问题的有效补充手段。

作者 | Maciej Domanski   译者 | 弯月

出品 | CSDN(ID:CSDNnews)

2022 年秋天,我们公司决定审核 Curl,这是一款使用非常广泛的命令行程序,可在服务器之间传输数据,而且还支持各种协议。当时恰逢公司的“创作周”,我们有比平时更多的人手,因此审核可以采用非标准的方法。

在讨论应用程序的威胁模型时,一位团队成员开玩笑说:“我们试过 Curl  AAAAAAAAAA……了吗?”尽管这是一句话玩笑话,但我们因此萌生了一个想法:我们应该对 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 中得到修复。

8ae58e2859d7664e94279d65c015a8a7.png

使用 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[]。这个参数指向一个以 null 结尾的指针数组,每个指针指向一个包含有关程序环境信息的字符串。

这三个参数的名称任意,因为它们只在所处的函数中有效。

Curl 的 main() 函数位于 curl/src/tool_main.c 文件中,它将命令行参数传递给函数 operate(),该函数会解析这些参数,并设置 Curl 的全局配置。然后 Curl 使用这些全局配置来执行操作。

b8513b1e18026d434e5ef18276211b16.png

25d3e443230759a9983a2f8eddeb8acd.png

argv 的模糊测试

在尝试对 Curl 进行模糊测试时,我四处寻找方法,使用 AFL 对 Curl 的参数解析进行模糊测试。我找到了 AFL 的创建者 Michal Zalewski 的一段话:

“AFL 不支持 argv 模糊测试,因为老实说这种测试在实践中并没有太大用处。experimental/argv_fuzzing/ 中有一个示例,展示了如何进行这种测试。”

我查看了 AFL 的这个实验性功能以及 AFL++ 中同等的功能。argv 模糊测试功能可以测试从 CLI 传递给程序的参数(注意不是标准输入)。当你想在模糊测试中覆盖一个库的多个 API 时,就可以对使用这个库的工具的参数进行模糊测试,而无需为每个 API 编写多个模糊测试。

730bed2599585ae47e0d4c0c9ddf11c1.png

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_IJNIT_SET0,如下所示:

9b5093e9a9e008effa9f22b4149b5ba2.png

curl/src/tool_main.c

32f2821b54d48a33c5cabcc7a9591ea4.png

准备字典

模糊字典文件用于指定模糊测试引擎在测试过程中需要使用的数据。模糊测试引擎会调整其策略,以处理字典中的词。在 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

0ce0790f99b98ca2f147a981e982110a.png

为 Curl 连接设置服务

我一开始的目标只是给命令行做模糊测试。但我仍然需要考虑模糊测试工具生成的每个有效的 Curl 命令是否会真的连接到某个远程服务。为了避免连接到真正的服务,同时保证能够测试到负责连接的代码,我使用 netat 工具生成了一个模拟的远程服务。首先,我修改了机器配置,将所有出网流量重定向到 netcat 监听的端口。

我用下述命令在后台运行 netcat:

$ netcat -l 80 -k -w 0 &

上述参数表明,该服务会在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 -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。

09e62d1f3de01e31dc0a2bafab5636b9.png

结果分析

模糊测试在一台32核的 Intel Xeon Platnum 8280 CPU @ 2.70GHz 的机器上运行了一个月。运行期间发现了下面的问题,大多数都是在模糊测试开始后几个小时内发现的。

  • 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(当使用并行选项和特定字符序列时的释放后使用问题)

当 Curl 使用并行选项(-z)时,如果遇到不匹配的大括号以及两个连续的、能创建51个主机的字符序列时,就会触发一个释放后使用问题。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

08595350b5be91167e976c1992c5bfc3.png

Dockerfile

如果你想学习设置模糊测试工具的完整过程,并尝试对 Curl 的命令行参数进行模糊测试,可以使用以下 Dockerfile:

# syntax=docker/dockerfile:1
FROM aflplusplus/aflplusplus:4.05c
RUN apt-get update && apt-get install -y libssl-dev netcat iptables groff
# Clone a curl repository
RUN git clone https://github.com/curl/curl.git && cd curl && git checkout 2ca0530a4d4bd1e1ccb9c876e954d8dc9a87da4a
# Apply a patch to use afl++ argv fuzzing feature
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 
# Apply a patch to use afl++ argv fuzzing feature
RUN cd curl && git apply curl_argv_fuzz.patch
# Compile a curl using collision-free instrumentation at link time and ASAN
RUN cd curl && \
    autoreconf -i && \
    CC="afl-clang-lto" CFLAGS="-fsanitize=address -g" ./configure --with-openssl --disable-shared && \
    make -j $(nproc) && \
    make install
# Download a dictionary
RUN wget 
https://gist.githubusercontent.com/ahpaleus/f94eca6b29ca8824cf6e5a160379612b/raw/3de91b2dfc5ddd8b4b2357b0eb7fbcdc257384c4/curl.dict
COPY <<-EOT script.sh
    #!/bin/bash
    # Running a netcat listener on port tcp port 80 in the background
    netcat -l 80 -k -w 0 &
    # Prepare iptables entries
    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
    # Prepare fuzzing directories
    mkdir fuzz &&
          cd fuzz &&
          mkdir in out &&
          echo -ne 'curl\x00http://127.0.0.1:80' > in/example_command.txt &&
          # Run afl++ fuzzer
          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

362ff56db9d81f8d86a65194667a602f.png

总结

总的来说,我们的方法证明了对命令行进行模糊测试是一个发现软件问题的有效补充手段。尽管最初人们怀疑其有效性,但我们的结果确实提供了有价值的见解。我们相信,这种方法可以增强命令行工具的安全性,尽管 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)。

 
 

5798275f340558cbeda7130921fd802c.gif

 
 

c6b433281435043b04a9ea53e3f8cc4a.jpeg

☞华为起诉小米专利侵权,国家知识产权局已受理;iPhone 等设备电池正式涨价;FFmpeg 6.0 发布|极客头条
☞万亿模型训练需 1.7TB 存储,腾讯混元如何突破 GPU 极限?
☞没有这些,别妄谈做 ChatGPT 了
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐