在之前关于基于套接字的模糊测试技术的博客文章中,我解释了如果对FTP服务器进行模糊测试,并详细说明了我是如何对FreeRDP进行模糊测试。在我们基于套接字的模糊测试技术系列的第三部分也是最后一部分中,我将重点关注 HTTP 协议,更具体地说,我将针对Apache HTTP 服务器进行测试
作为最流行的 Web 服务器之一,Apache HTTP 服务器不需要任何介绍。Apache HTTP 是最早的 HTTP 服务器之一,其开发可追溯到 1995 年。截至 2021 年 1 月,它的市场份额为 26%,是互联网上使用量第二大的 Web 服务器——目前运行在超过300.000.000台服务器上——仅略微落后于 Nginx (31%)
我将分三部分详细介绍我的 Apache fuzzing 研究。在第一部分中,我将简要介绍 Apache HTTP 的工作原理,并让您深入了解自定义 mutator 以及如何将它们有效地应用于 HTTP 协议
我们开始吧!
一、自定义突变器
与单纯的随机生成输入相比,基于突变的模糊测试对现有的输入引入了微小的变化,这些变化可能仍然保持输入有效,但会产生新的行为。这就是我们所说的“mutators”
默认情况下,AFL fuzzer 实现了基本的突变机制,如位翻转、字节递增/递减、简单运算或块拼接。这些mutator总体上实现了良好的效果,尤其是在二进制格式中,但是当应用于基于文法的格式(如 HTTP)时,它们的有效率一般。这就是为什么我决定创建一些额外的突变器,专门用于对 HTTP 协议进行模糊测试的任务。您可以在以下链接中找到代码:
https://github.com/antonio-morales/Apache-HTTP-Fuzzing/tree/main/Custom%20mutators
我在本练习中关注的一些突变策略包括:
- 拼接变换(Piece swapping):交换两个不同请求的部分
- 行交换(Line swapping): 交换两个不同 HTTP 请求的行
- 字交换(Word swapping): 交换两个不同HTTP请求的单词
- 字符集暴力替换(Charsets bruteforce):对某些字符集进行暴力遍历替换
- 以1字节为字符集的暴力替换(1-byte bruteforce):
0x00 – 0xFF
- 以2字节为字符集的暴力替换(2 bytes bruteforce):
0x0000 – 0xFFFF
- 以3 个字母为字符集的暴力替换(3 letters bruteforce):
[a-z]{3}
- 以4个数字为字符集的暴力替换(4 digits bruteforce):
[0-9]{4}
- 以3 个字母和1个数字为字符集的暴力替换(3 letters & numbers bruteforce):
([a-z][0-9){3}
- 以3字节或者4字节字符串为字符集的暴力替换(3bytes / 4 bytes strings bruteforce):将输入文件中的所有 3/4 字节字符串进行暴力替换
- 以1字节为字符集的暴力替换(1-byte bruteforce):
您可以在这里找到为了能够使用这些自定义突变器而需要包含的附加函数:
https://github.com/antonio-morales/Apache-HTTP-Fuzzing/blob/main/Custom%20mutators/Mutators_aux.c
二、覆盖率比较
在我们能够保证我们的突变器能够用于完整的长期模糊测试工作之前,我们需要去验证我们自定义的mutator是否有效。
考虑到这一点,我使用自定义突变器的不同组合进行了一系列模糊测试。我的目标是找到在24 小时内提供更高代码覆盖率的变异器组合。
起始覆盖率如下(仅使用原始输入语料):
- 行数:30.5%
- 函数:40.7%
这些是 24 小时后每个mutator组合的结果(所有测试均使用以下参数进行AFL_DISABLE_TRIM=1
和-s 123
)
这里没有列出显示了更糟糕结果的mutator组合,且并没有将其列入考虑范围。如您所见,Line Mixing + AFL HAVOC是获胜的组合
之后,我通过增加启用的 Apache Mod 的数量进行了第二次测试。Line Mixing + HAVOC测试再次成为获胜的组合
虽然这是获胜的组合,但这并不意味着我只使用了这个自定义的mutator。在整个Apache HTTP 模糊测试过程中,我使用了所有可用的自定义突变器,因为我的目标是获得最高的代码覆盖率。在这种情况下,mutator 效率变得不那么重要了
三、自定义语法
另一种方法是使用基于语法的突变器。除了使用自定义突变器之外,我将一个自定义语法添加到 AFL++工具中用来模糊 HTTP:Grammar-Mutator
使用 Grammar-Mutator 非常简单:
make GRAMMAR_FILE=grammars/http.json
./grammar_generator-http 100 100 ./seeds ./trees
然后
export AFL_CUSTOM_MUTATOR_LIBRARY=./libgrammarmutator-http.so
export AFL_CUSTOM_MUTATOR_ONLY=1
afl-fuzz …
就我而言,我创建了一个简化的 HTTP 语法规范:
我已经包含了最常见的 HTTP关键词(GET
、HEAD
、PUT
、 …)。这个语法中,我也使用了单字节的字符串,然后在后面的阶段,我使用Radamsa来增加这些字符串的长度。Radamsa是另一个通用模糊器,最近作为自定义 mutator库添加到 AFL++ 中。同样,我在这里省略了大部分附加字符串,并选择将它们包含在字典中
四、Apache配置
默认情况下,Apache HTTP 服务器是通过编辑[install_path]/conf
文件夹中包含的文本文件来配置的。通常主配置文件被命名为httpd.conf
,它每行包含一个指令。此外,可以使用Include
添加其他配置文件,并且也可以使用通配符来包含许多配置文件。。反斜杠“\”可以用作一行的最后一个字符,表示指令继续到下一行,并且在反斜杠和行尾之间不能有其他字符或空格
4.1 模块、模块和更多模块
Apache 具有模块化架构。您可以启用或禁用模块以添加和删除 Web 服务器功能。除了默认与 Apache HTTP 服务器捆绑的模块外,还有大量第三方模块,提供扩展功能
要在 Apache 构建中启用特定模块,请在构建过程的生成配置文件阶段中使用标志--enable-[mod]
:
./configure --enable-[mod]
[mod]
中的内容我们要构建中启用的模块的名称
我使用了一种增量方法:我从一组启用了--enable-mods-static=few
的小模块开始,在达到稳定的模糊测试工作流程后,我启用了一个新模块并再次测试了模糊测试的稳定性。此外,我启用了--enable-[mod]=static
和--enable-static-support
来使得Apache模块是以静态链接的方式的构建,从而显着提高了模糊测试速度
在构建步骤之后,我们可以定义这些模块应该在哪个上下文中发挥作用。为此,我修改了httpd.conf
文件并将每个模块链接到不同且唯一的Location
(目录或文件)。这样,我们就有了不同的服务器路径指向的不同 Apache 模块
为了使模糊器的工作更轻松,我将我的htdocs
文件夹中包含的大多数文件的文件名长度修改为1到2个字节。这使得AFL++能够更轻松猜测有效的URL请求
例如:
GET /a HTTP 1.0
POST /b HTTP 1.1
HEAD /c HTTP 1.1
在模糊测试时,我尝试启用最大数量的 Apache 模块,目的是检测模块间的并发错误
五、更大的字典,冲
我在尝试对Apache进行模糊测试时发现了一个限制条件,AFL可以正确管理的最大字典条目数限制为 200
挑战在于,对我在httpd.conf
中包含的每个新模块及其相应位置,我还需要添加它们各自的字典条目。例如,如果我在“mod_crypto”位置添加了一个新的“scripts”文件夹,我还需要向scripts
字典添加一个新字符串。此外,一些模块(例如,webdav
),也需要大量的新HTTP关键词(PROPFIND
,PROPPATCH
等)
出于这个原因,考虑到更大的字典在其他场景中也很有用,我向AFL++ 项目提交了一个拉取请求来添加这个功能
这会产生一个新的AFL_MAX_DET_EXTRAS环境变量,它允许我们设置以正确方式使用的最大字典条目数。你可以在这里找到我使用的字典之一
在本系列的第二部分,我们将展示一种更有效的方法来处理文件系统的系统调用,并介绍“文件监视器”的概念
六、代码更改
6.1 MPM模糊测试
Apache HTTP Server 2.0 将其模块化设计扩展到 Web 服务器的最基本功能。服务器附带了一系列多进程处理模块 (MPM),这些模块负责绑定到机器上的网络端口、接受请求并分派子进程来处理请求。您可以在 https://httpd.apache.org/docs/2.4/mpm.html 上找到有关 Apache MPM 的更多信息
在基于 Unix 的操作系统中,Apache HTTP服务器默认配置MPM 事件,尽管我们可以通过--with-mpm=[choice]
配置标志选择要使用的MPM版本。每个MPM模块在多线程和多进程处理方面都有不同的特性。因此,我们的模糊测试方法将因使用的MPM配置而异
我对这两个配置进行了模糊测试:
- Event MPM(多进程和多线程)
- Prefork MPM(单一控制过程)
在开始我们的模糊测试所需的代码更改方面,我采用了一种新方法,不是再将本地文件描述符替换套接字来提供我们的模糊测试输入。我的新方法是创建了一个的本地网络连接并通过它发送模糊测试输入(感谢@n30m1nd的启发!)
6.2 我们对传统代码的修改
有关有效模糊网络服务器所需的一般代码更改,请查看之前的系列文章。但是,这里请回顾一些关于这些更改中最重要更改的摘要
一般来说,这些变化可以分为:
- 旨在减少熵的变化
- 旨在减少延迟的变化:
- 删除一些
sleep()
和select()
调用
- 删除一些
您可以通过检查以下补丁来查看有关这些更改的所有详细信息:
七、“假”错误:当你的工具欺骗你时
最初在Apache HTTP 中被认为似乎是一个简单的错误,在分析后结果却变得更加复杂。我将详细介绍我在探索海森堡Bug兔子洞中的旅程,因为这是一个很好的例子,说明有时候进行根本原因的分析有时是多么令人沮丧。此外,我认为这些信息对于其他安全研究人员可能非常有用,他们可能处于相同的情况,您不确定该错误实际上存在于目标软件中还是在您的工具中
当我检测到一个只能在 AFL++ 运行时重现的错误时,这个故事就开始了。当我尝试直接在 Apache 的 httpd 二进制文件上重现它时,服务器没有崩溃。在这一点上,我脑海中闪过的第一个想法是我正在处理一个非确定性错误。换句话说,一个错误只发生在 N 种情况中的一种。所以,我做的第一件事是创建一个脚本,该脚本启动应用程序 10,000 次并将其 stdout 输出重定向到一个文件。但是还是没有出现这个bug。我将执行次数增加到 100,000,但我们的错误重现仍然难以捉摸
奇怪的是,每次我在 AFL++ 下运行它时,都会一直触发该错误。所以我考虑了环境和 ASAN 的影响,这可能是我们的神秘错误的罪魁祸首。但是在对这个假设进行了数小时的深入研究后,我仍然无法找到可靠地重现错误所需的条件
·我开始怀疑我的工具可能在欺骗我,于是我决定使用 GDB 更深入地调查这个候选错误
这个错误产生的原因貌似是在被定义在sanitizer_stackdepotbase.h
中的find
函数。该文件是ASAN库的一部分,每次将新项目推入程序堆栈时都会调用该文件。但是,由于某种原因,s
链表损坏了。结果,由于s->link
表达式试图取消引用无效的内存地址,因此发生了段错误
我可能会在ASAN库中遇到一个新的bug吗?这对我来说似乎不太可能,但随着我花更多的时间去研究漏洞,它就越来越成为一个合理的解释。好的一面是,我能学到很多关于ASAN内部的知识
然而,我在试图找到链表损坏的源头时遇到了严重的困难。是Apache的错还是AFL++的错?在这一点上,我转向了rr 调试器。rr 是 Linux 的调试工具,旨在记录和重放程序执行,即所谓的反向执行调试器。rr允许我“倒退”并找到错误的根本原因
最后,我终于可以解释我们神秘的内存损坏bug的起源。AFL++ 使用共享内存位图来获取程序的覆盖进度。它在程序分支点插桩的代码本质上等同于:
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
默认情况下,这个位图的大小是64kb,但是正如您在图中看到的那样,我们guard
变量中的值为 65576 。因此在这种情况下,AFL++ fuzzer可能会溢出__afl_area_ptr
数组,导致覆盖掉程序的内存。如果我们尝试使用小于所需的最小值的位图尺寸,AFL++ 通常会发出警报。但在我们遇到的这种特殊情况下,它并没有这样做。原因我不知道,答案要留给历史来提供了
解决这个错误最终就像设置环境变量MAP_SIZE=256000一样简单。我希望这则轶事能帮助其他人,并提醒他们有时您的工具可能会欺骗您!
八、Apache 模糊测试 TL;DR
对于那些喜欢直截了当(我不推介这样!)的人,以下是您自己开始对 Apache HTTP 进行模糊测试所需的知识:
- 将补丁应用到源代码:
patch -p2 < /Patches/Patch1.patch
patch -p2 < /Patches/Patch2.patch
- 配置和构建 Apache HTTP:
$ CC=afl-clang-fast CXX=afl-clang-fast++ CFLAGS="-g -fsanitize=address,undefined -fno-sanitize-recover=all" CXXFLAGS="-g -fsanitize=address,undefined -fno-sanitize-recover=all" LDFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -lm" ./configure --prefix='/home/user/httpd-trunk/install' --with-included-apr --enable-static-support --enable-mods-static=few --disable-pie --enable-debugger-mode --with-mpm=prefork --enable-negotiation=static --enable-auth-form=static --enable-session=static --enable-request=static --enable-rewrite=static --enable-auth_digest=static --enable-deflate=static --enable-brotli=static --enable-crypto=static --with-crypto --with-openssl --enable-proxy_html=static --enable-xml2enc=static --enable-cache=static --enable-cache-disk=static --enable-data=static --enable-substitute=static --enable-ratelimit=static --enable-dav=static
$ make -j8
$ make install
- 运行模糊器:
AFL_MAP_SIZE=256000 SHOW_HOOKS=1 ASAN_OPTIONS=detect_leaks=0,abort_on_error=1,symbolize=0,debug=true,check_initialization_order=true,detect_stack_use_after_return=true,strict_string_checks=true,detect_invalid_pointer_pairs=2 AFL_DISABLE_TRIM=1 ./afl-fuzz -t 2000 -m none -i '/home/antonio/Downloads/httpd-trunk/AFL/afl_in/' -o '/home/antonio/Downloads/httpd-trunk/AFL/afl_out_40' -- '/home/antonio/Downloads/httpd-trunk/install/bin/httpd' -X @@
九、待续…
请留意本系列的第二部分,在那里我将深入探讨其他有趣的模糊测试方面,例如自定义拦截器和文件监视器。我还将解释我如何设法模糊一些特殊的 mod,例如mod_dav
或mod_cache
下一篇文章见!