导语:在上一篇文章中我通过介绍如何使用American Fuzzy Lop对apache httpd服务进行模糊测试。不过令我吃惊的是,我写完这篇文章之后,我发现了在fuzz过程中有几处崩溃的地方。

介绍

在上一篇文章中我通过介绍如何使用American
Fuzzy Lop对apache
httpd服务进行模糊测试。不过令我吃惊的是,我写完这篇文章之后,我发现了在fuzz过程中有几处崩溃的地方。之所以令我吃惊,是因为在我之前进行fuzz的人,都可以发现这一漏洞,不过我却是第一个提出来,所以我就写了这一篇博客。

目标

在fuzz过程中发现apache httpd服务在AFL下崩溃掉了,导致出现了很多问题,比如模糊测试程序稳定性下降,模糊测试之外的程序不会崩溃等等。在这篇文章中,我会在展示漏洞的同时尝试解释这些问题,最后再对崩溃本身进行一定的说明。

测试用例

由于这只是我自己用AFL来模拟继续程序的网络模糊测试,所以我没有考虑太多复杂以及覆盖范围广的例子。

所以,为了获得大量安装apache httpd服务的案例,我决定使用wiki中提供的headers进行搜索。

bash招式一:cut命令

我做的第一件事就是使用编辑器将在Request
Fields中的两个表放到一个文本文件中。这一步骤中编辑器不能将tab换成空格,否则cut命令将执行不成功。我选择的文件名为wiki-http-headers,将内容写入后,我们可以选择第三列的表,命令如下,不过请注意,cut默认分隔符是tab: 

cat wiki-http-headers | cut -f3 | grep ":" | sed "s#Example....##g" | sort -u

bash招式二:for命令

现在我们已经学了第一个招式,现在我们使用迭代的方法将每一个header都独立成行,以及每行创建一个测试用例。熟悉bash用户肯定知道如何做到这一点,命令如下:

a=0 && IFS=$'n' && for header in $(cat wiki-http-headers | cut -f3 | grep ":" | sort -u); do echo -e "GET / HTTP/1.0rn$headerrnrn" > "testcase$a.req";a=$(($a+1)); done && unset IFS

我来简单快速的解释一下这一条命令,有一个叫做内部字段分隔符的东西:IFS,它是一个环境变量,用于分割各个字段。默认情况下,IFS可以被认为是空格,tab,以及换行符。IFS在遇到空格时,会干扰头文件进行分割,因为for命令会遍历给定的文件列表,所以这就是我们为什么将IFS定义成换行符的原因。现在我们已经写好迭代,并且将每一个头文件都写入到不同的文件当中去了。

bash招式 视频详细讲解:https://asciinema.org/a/12d5frpz4sqgwr2azyugrhrm3/embed?

模糊测试

现在我们已经收集了足够多的测试用例,所以我们现在可以开始模糊测试了。这一部分的篇幅比较少,因为上一篇文章中已经介绍了足够多关于模糊测试的内容,基本的步骤为:

1.下载apr, apr-utils, nghttpd2, pcre-8以及 Apache httpd 2.4.25
2.通过以下命令安装依赖:

sudo apt install pkg-config
sudo apt install libssl-dev

3.为apache打补丁:https://gist.github.com/n30m1nd/ec19fc17c6293be303b85a998cd05aa9
4.编译相应的标志,以及安装路径

现在一切都准备好了,开始进行模糊测试了,正如以下视频可以看到,经过一点点改进,崩溃并不需要多长时间:

视频地址:https://asciinema.org/a/Q4gkkvOwlHABXqRDTV4VYU7jP/embed?

值得一提的是我被这一崩溃欺骗过一次,因为之前我已经介绍过这种测试案例,也知道他很快就会崩溃。之后我才在检查AFL文件夹的稳定性以及可变行为中通过honggfuzz,radamsa以及AFL组合发现这一测试案例的。

崩溃解释

对第一次测试结果不满意

首先,在测试中遇到崩溃测试案例时,必须检测他是不是存在漏洞,那么现在让我们来试试吧:
视频如下:https://asciinema.org/a/sfcFzCytHGmbZU4YkjAM8aY5t/embed?

不过..崩溃没有在apache外部显示,那么发生了什么呢?

故障排除

有下面几件事情我们需要做:
首先,我们现在在持续模式中进行模糊测试:
这就意味着我们的测试案例确实让软件进行崩溃了,但是这只是很多崩溃中的一个。在我们这种情况中,__AFL_LOOP设置的值已经超过9000。对于不知道设置变量值的情况,我们可以通过迭代确定这一变量值,比如输入8999时,软件没有崩溃,不过输入9000之后,软件崩溃,那么现在只有一个崩溃案例。

第二件要考虑的事为AFL报告的稳定性:
在模糊测试中,我们得到的报告稳定性越来越低。比较低的稳定性会时AFL在代码中使用随机数,或者日期函数,甚至为初始化的内存。

第三件事为我们分配给模糊测试进程的内存:
这种情况下由于我们使用”-m none”参数运行afl因此内存使用是没有限制的。但是在其他情况下,内存使用可能会产生溢出,然后访问没有初始化的内存。

判断__AFL_LOOP变量值

为了测试我们第一个假设,我们需要测试更多的崩溃案例。缩小参数值,有助于减少时间去寻找新的路径,更加便于测试。

视频地址为:https://asciinema.org/a/Q4gkkvOwlHABXqRDTV4VYU7jP/embed?

现在验证我们第二个假设

测试过程目标:保持稳定

在测试过程中,一旦越来越多的AFL进入到崩溃的测试用例,我们可以检查崩溃性是否下降,我们可以了解到内存和崩溃存在某种联系。为了测试我们是否真的使用了未初始化的内存,我们可以使用一个工具,名为Valgrind。
Valgrind是由多种程序进行组合,并且对你的程序进行检查。默认情况下,它会使用”memcheck”,一种检查内存管理的工具。
在debian8中,只需要执行下方命令就可以:

sudo apt install valgrind

安装完成之后我们需要在valgrind下运行apache,命令为:

NO_FUZZ=1 valgrind -- /usr/local/apache-afl-persist/bin/httpd -X

NO_FUZZ环境遍令是由补丁中的代码进行读取,是为了防止模糊测试进入死循环。在启动apache之后,我们需要将测试用例发送到apache服务中。希望这一步骤能够验证我们第二个假设,
视频地址为:https://asciinema.org/a/tdOiIZZDYpXafeeJiQiRuQ3jp/embed?

可以看到,apache确实在使用没有初始化的变量。不过这个时候apache没有崩溃,那么现在使用之前产生的所有测试用例进行apache测试:
视频地址为:https://asciinema.org/a/s4HRv61oFcNCmbdQejYVEMGWt/embed?

漂亮,现在出现崩溃了,我们可以进行下一步判断了

找到源头

我们做了一个快速的分析,看看是哪一个header导致的崩溃

gdb+valgrind

关于valgrind一个很酷的事情就是在程序出现错误时,它可以显示程序当前的运行状态,以便你进行分析。我们可以通过–vgdb-error=1这一参数实现,作用为当错误发生时,程序会停止,然后等待调试器的发出的下一条指令。在这种情况下,这一参数好像对我们来讲是量身定做的,因为我们当前运行的程序正在访问未初始化的值,并且造成缓冲区溢出。所以valgrind进行报错,我们可以对错误进行调试。
要使用此功能,我们需要在命令行中运行:

NO_FUZZ=1 valgrind --vgdb-error=0 -- /usr/local/apache_afl_blogpost/bin/httpd -X

然后在另外一个终端中,发送我们触发错误的内容:

cat crashing-testcase.req | nc localhost 8080

最后,在第三个终端中,启动gdb,并进入到valgrind进程中:

target remote | /usr/lib/valgrind/../../bin/vgdb

现在我们就可以观察到崩溃时,apache的状态到底是什么:

blogCVE-gdb_session_1.png

在图片中,1693行爆出了第一个错误,我的直觉告诉我s变量是产生错误的原因,因为在累加的过程中没有对的值除了*s判断指向空,其余没有任何安全性检查。由于s在编译过程中被优化,所以我们需要通过上一级检查s将要指向的conn变量。给读者留下一个小练习,为什么pwndbg所示的内容跟“bt”命令所显示的不同。

在下一张图片当中,请记住上图中两个高亮的变量值:0x6e2990c和8749

blogCVE-valgrind-error1.png

这里是我们需要分析的地方,从图一得到的两个图片,8749在这里是有作用的,上图我们可以到conn遍历那个在0x6e2990c中分配了8192个字节,明显比8749小,所以会造成溢出。

blogCVE-valgrind_error_2.png

我们会解释为什么会出现8749个字节,通过continue命令让valgrind进入到下一个错误当中并且将错误显示出来。0x6e2bb39读取无效,’conn’变量初始化指针地址为:0x6e2990c。现在我们需要做一些简单的数学计算出s变量值:

invalid_read_offset = valgrind_error_pointer - conn

带入数值为:

8749 = 0x6e2bb39 - 0x6e2990c

记录以及重放框架

在分类过程中,发现了几个可能阻碍调试过程的问题:apache进入到无限循环当中,valgrind会在崩溃地方加上它自己的函数包装器,所以这个堆栈会和运行在gdb上的有所不同。
这记录以及重放框架就会派上用场,它会确定性的重播程序状态。你甚至可以向后回退执行程序。在这种情况下,我们可以在错误发生之后,重复测试这一错误:
视频地址如下:https://asciinema.org/a/WKR1sDZsBH1C0lYuQXUinj1LN/embed?

结论

现在我们已经学会了如何使用bash来有效地从Web上检测出来测试用例,并相信即使有数百人可能会弄乱一些软件,我们仍然可以在使用正确的工具组合时增加价值,掌握很多知识。

源链接

Hacking more

...