我们如何将高级模糊测试技术应用于 cURL

2022 年末,Trail of Bits 受开源技术改进基金(OSTIF)委托,对 cURL 文件传输命令行实用程序及其库 libcurl 进行安全评估进行安全评估。参与范围包括代码审查、威胁模型以及本博客文章的主题:分析和改进 cURL 模糊测试代码的工程工作。

讨论了此过程的几个要素,包括如何识别代码库中缺乏覆盖的重要区域,然后修改模糊测试代码以覆盖这些遗漏区域。例如,通过在模糊器初始化期间设置某些 libcurl 选项并引入新的种子文件,将 HTTP 严格传输安全(HSTS)处理代码的行覆盖率翻倍,并将Alt-Svc标头的覆盖率增加五倍。还扩展了模糊协议集以包括 WebSocket 并启用了许多新的 libcurl 选项的模糊测试。最后解释了 cURL 团队可以采用的一些更复杂的模糊测试技术,以进一步提高覆盖率、将模糊测试引入 cURL 命令行并减少当前测试用例格式固有的低效率。

cURL 如何进行模糊测试?

OSS-Fuzz 是 Google 为开源项目提供的免费服务,是 cURL 的持续模糊测试基础架构。它支持 C/C++、Rust、Go、Python 和 Java 代码库,并使用覆盖率引导的 libFuzzer、AFL++和 Honggfuzz 模糊测试引擎。OSS-Fuzz 于 2017 年 7 月 1 日采用 cURL,合并的代码位于 GitHub 上的[curl-fuzzer](https://github.com/curl/curl-fuzzer)存储库中,这是参与此部分的重点。

该存储库包含模糊测试 cURL 和 libcurl 所需的代码(设置脚本、测试用例生成器、工具等)和语料库(初始测试用例集)。它旨在模糊测试各个目标,即 libcurl 支持的协议,如 HTTP(S)、WebSocket 和 FTP。curl-fuzzer 下载最新的 cURL 及其依赖项副本,针对它们编译它们并为这些目标构建二进制文件。

每个目标都接受一个特殊结构的输入文件,使用对 libcurl 的适当调用处理它并退出。与每个目标相关联的是一个语料库目录,其中包含用于要模糊测试的协议的有趣种子文件。这些文件使用自定义类型长度值(TLV)格式进行结构化,不仅编码原始协议数据,还编码协议的特定字段和元数据。例如,HTTP 协议的模糊器包括协议版本、自定义标头以及 libcurl 是否应遵循重定向的选项。

第一印象:HSTS 和 Alt-Svc

任务是分析和改进模糊器对提供 curl 内部的库 libcurl 的覆盖范围。首先想到的明显问题是:当前的覆盖范围是什么样的?为了回答这个问题,想查看 OSS-Fuzz 定期生成的报告中提供的最新覆盖数据。在可公开访问的 oss-fuzz-coverage Google Cloud Storage 存储桶的 URL 上进行一些探索后,能够找到 cURL 的覆盖报告(以后可以通过OSS-Fuzz 检查器页面访问)。这是 2022 年 9 月 28 日的一份报告,在开始参与时。

阅读报告时,很快注意到几个源文件几乎没有得到覆盖,包括一些实现安全功能或负责处理不受信任数据的文件。例如,hsts.c 提供了解析和处理[Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)响应头的函数,在 OSS-Fuzz 上运行五年多后,仅具有 4.46%的行覆盖率、18.75%的函数覆盖率和 2.56%的区域覆盖率:

负责处理Alt-Svc响应头的文件 altsvc.c 也存在覆盖不足的问题:

对模糊测试代码的调查揭示了这些数字如此低的原因。第一个问题是语料库目录缺少包含Strict-Transport-SecurityAlt-Svc标头的测试用例,这意味着模糊器无法快速跳入测试代码库的这些区域以查找错误;它必须使用覆盖反馈自行构造这些测试用例,这通常是一个较慢的过程。

第二个问题是模糊器从未设置[CURLOPT_HSTS](https://curl.se/libcurl/c/CURLOPT_HSTS.html)选项,该选项指示 libcurl 使用 HSTS 缓存文件。结果,在模糊器运行期间从未启用 HSTS,并且hsts.c中的大多数代码路径从未被命中。

实现 HSTS 良好覆盖的最后一个障碍是其规范的问题,该规范告诉用户代理在通过未加密的 HTTP 发送时忽略Strict-Transport-Security标头。然而,这在模糊测试的上下文中造成了一个问题:从模糊测试目标的角度来看,它从未建立实际的 TLS 连接,因此每个连接都是未加密的,并且Strict-Transport-Security始终被忽略。对于Alt-Svc,libcurl 已经包含了一个解决方法,当设置了某个环境变量时,放宽调试构建的 HTTPS 要求(尽管 curl-fuzzer 未设置此变量)。因此,解决此问题只是在 libcurl 中添加类似功能并确保 curl-fuzzer 设置所有必要的环境变量的问题。

解决这些问题的更改如下:

  1. 向 curl-fuzzer 添加了Strict-Transport-SecurityAlt-Svc的种子文件(ee7fad2)。
  2. 在 curl-fuzzer 中启用CURLOPT_HSTS(0dc42e4)。
  3. 添加了一个检查,以允许 libcurl 的调试构建在设置CURL_HSTS_HTTP环境变量时绕过 HSTS 的 HTTPS 限制,并在 curl-fuzzer 中设置CURL_HSTS_HTTPCURL_ALTSVC_HTTP环境变量(6efb6b1937597c)。

在更改合并到上游的第二天,OSS-Fuzz报告这两个文件的覆盖率显着增加:

一年多的模糊测试后(在2024 年 1 月 29 日),我们的三个修复将hsts.c的行覆盖率翻倍,并将altsvc.c的覆盖率增加近五倍:

播种漏洞的种子

进一步探索 curl-fuzzer,看到了许多其他提高覆盖率的机会。发现的一个容易实现的目标是语料库目录中找到的种子文件集。虽然 libcurl 支持众多协议(其中一些让我们感到惊讶!)和功能,但并非所有这些都在语料库中表示为种子文件。这很重要:正如之前提到的,一组全面的初始测试用例,涉及尽可能多的主要功能,是获得覆盖范围的捷径,并大大减少在发现错误之前花费的模糊测试时间。

创建新种子文件的功能,希望促进新的覆盖范围,包括(ee7fad2):

  • [CURLOPT_LOGIN_OPTIONS](https://curl.se/libcurl/c/CURLOPT_LOGIN_OPTIONS.html):为 IMAP、LDAP、POP3 和 SMTP 设置协议特定的登录选项
  • [CURLOPT_XOAUTH2_BEARER](https://curl.se/libcurl/c/CURLOPT_XOAUTH2_BEARER.html):指定要与 HTTP、IMAP、LDAP、POP3 和 SMTP 服务器一起使用的 OAuth 2.0 承载访问令牌
  • [CURLOPT_USERPWD](https://curl.se/libcurl/c/CURLOPT_USERPWD.html):指定用于身份验证的用户名和密码
  • [CURLOPT_USERAGENT](https://curl.se/libcurl/c/CURLOPT_USERAGENT.html):指定 User-Agent 标头的值
  • [CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256](https://curl.se/libcurl/c/CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256.html):为 SSH 连接设置远程服务器的预期 SHA256 哈希
  • [CURLOPT_HTTPPOST](https://curl.se/libcurl/c/CURLOPT_HTTPPOST.html):设置 POST 请求数据。curl-fuzzer 仅使用CURLOPT_MIMEPOST选项来实现此目的,而类似但已弃用的CURLOPT_HTTPPOST选项未被使用。还添加了对此旧方法的支持。

某些其他 CURLOPT,与上一节中的 CURLOPT_HSTS 一样,在模糊器的初始化函数中全局设置更有意义。这些包括:

  • [CURLOPT_COOKIEFILE](https://curl.se/libcurl/c/CURLOPT_COOKIEFILE.html):指向要从中读取 cookie 的文件名。它还允许模糊测试负责将内存中的 cookie 保存到文件的代码
  • [CURLOPT_COOKIEJAR](https://curl.se/libcurl/c/CURLOPT_COOKIEJAR.html):允许模糊测试将内存中的 cookie 保存到文件的代码
  • [CURLOPT_CRLFILE](https://curl.se/libcurl/c/CURLOPT_CRLFILE.html):指定要读取的用于 TLS 连接的证书吊销列表文件

从这里去哪里

随着对 curl-fuzzer 内部的了解增加,制定了几个提高模糊器功效的战略建议,但参与时间不允许我们自己实施。在最终报告中向 cURL 团队提出了这些建议,并在下面扩展了其中的一些。

字典

字典是 libFuzzer 的一个功能,对于 libcurl 所说的基于文本的协议特别有用。协议的字典是一个枚举在协议上下文中有趣的字符串的文件,例如关键字、分隔符和转义字符。向 libFuzzer 提供字典可以提高其搜索速度并导致更快地发现新漏洞。

curl-fuzzer 已经利用此功能针对 HTTP 目标,但目前不为 libcurl 支持的众多其他协议提供任何字典。建议 cURL 团队为这些协议创建字典以提高模糊器的速度。这可能是 LLM 的一个很好的用例;ChatGPT 可以响应以下提示生成起始字典(替换为目标协议的名称):
A dictionary can be used to guide the fuzzer. A dictionary is passed as a file to the fuzzer. The simplest input accepted by libFuzzer is an ASCII text file where each line consists of a quoted string. Strings can contain escaped byte sequences like "\xF7\xF8". Optionally, a key-value pair can be used like hex_value="\xF7\xF8" for documentation purposes. Comments are supported by starting a line with #. Write me an example dictionary file for a <PROTOCOL> parser.

argv 模糊测试

在首次接触 cURL 时,其中一人开玩笑说:“我们试过curl AAAAAAAAAA…了吗?”这个笑话背后有很多智慧;它促使我们对 cURL 的命令行界面(CLI)进行模糊测试,这产生了多个漏洞(见我们的博客文章,cURL 审计:一个笑话如何导致重大发现)。

此 CLI 模糊测试是使用 AFL++的[argv-fuzz-inl.h](https://github.com/AFLplusplus/AFLplusplus/blob/c0c985a2781f84313db80eea3662ec88fb264292/utils/argv_fuzzing/argv-fuzz-inl.h)头文件执行的。该头文件定义了宏,允许目标程序从标准输入的模糊器提供的数据构建包含命令行参数的argv数组。建议 cURL 团队使用 AFL++的此功能来持续模糊测试 cURL 的 CLI(实现细节可以在链接的博客文章中找到)。

结构感知模糊测试

curl-fuzzer 的一个弱点是其当前构造输入的方式所固有的,即使用自定义类型长度值(TLV)格式。TLV 方案(或类似的东西)对于模糊测试像 libcurl 这样支持大量全局和协议特定选项和参数的项目很有用,这些选项和参数需要在测试用例中进行编码。

然而,这种二进制格式的脆弱性使模糊器效率低下。这是因为 libFuzzer 不知道输入应遵循的结构。curl-fuzzer 期望输入数据采用严格的格式:一个 2 字节的记录类型字段(在参与时只有 52 个是有效的)、一个 4 字节的数据长度字段,最后是数据本身。由于 libFuzzer 不考虑此格式,它生成的大多数突变在 TLV 解包阶段最终无效,并且必须被丢弃。Google 的模糊测试指南警告出于这个原因不要使用 TLV 输入。

结果,用于引导突变进入有趣代码路径的覆盖反馈的性能比仅处理原始数据时差得多。实际上,libcurl 可能包含使用当前简单 TLV 策略永远不会发现的漏洞。

那么,cURL 团队如何在保持 TLV 格式的灵活性的同时解决此问题呢?进入[结构感知模糊测试](https://github.com/google/fuz...

阅读 13
0 条评论