译者:WisFree
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
介绍
在之前的一篇文章中,我详细地描述了如何使用American Fuzzy Lop(AFL-一款模糊测试工具)来对Apache的httpd服务器进行模糊测试。【文章链接】令我惊讶的是,其中的某些测试用例竟然崩掉了。我之所以觉得会“令我惊讶”,是因为在我之前肯定已经有人设计了能够正常运行的测试用例,但我的不知为何会崩溃。不管怎样,反正我是第一个报告这个漏洞的人!
目标
在AFL中查看了Apache httpd服务器的崩溃日志之后,我发现了很多问题。比如说,某些崩溃的测试用例是在模糊测试工具内部崩溃的,而且还影响了被测试程序的稳定性。在这篇文章中,我会跟大家解释测试用例崩溃的原因,并且告诉大家我是如何发现这些漏洞的。
测试用例
从维基百科收集到的测试用例
Bash-fu Taos
Valgrind for triage
Valgrind + gdb
rr
这些只是我使用AFL软件时所用的测试用例,而且我也没有使用非常复杂的测试用例(覆盖率不高)。为了能够设计出可以覆盖所有Apache httpd服务器普通安装场景的测试用例,我想出了一种非常简单的方法从维基百科的Header列表中爬取所有Header的方法。
Bash-fu Tao 1
我所做的第一件事就是将页面中“Request Fields”标签下的两个表格复制粘贴到一个text文件之中,编辑器随便你自己选,只要你的编辑器不会将TAB(制表符)替换成空格就行,否则cut命令将会失效。
我将这个text命名为“wiki-http-headers”,然后运行下列命令(选取表格中的第三列进行操作,记住cut命令的默认分隔符为TAB):
cat wiki-http-headers | cut -f3 | grep ":" | sed "s#Example....##g" | sort -u
我们可以看到,某些Header已经找不到了,例如TSVheader。我们现在可以忽略这些消失了的Header,然后继续进行我们的模糊测试,因为测试用例的覆盖率并不是我现在需要考虑的问题。其实在我们这个测试场景中,只要选出有针对性的测试用例就可以了。
Bash-fu Tao 2
接下来,我们需要对枚举每一个单独的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)的东西,它是一个环境变量,其中存储了Bash中用于进行字段划分的token。Bash中默认的内部字段分隔符是空格符、制表符和换行符。当遇到空格符的时候,这些分隔符将会影响Header,因为我们需要在Bash中迭代表格中的字段,所以我们需要将内部字段分隔符设置成换行符。现在,我们就可以迭代表格中的数据域,然后将每一个Header单独输出到不同的文件中。
Bash-fu Tao视频
下面是测试用例的创建过程:
模糊测试
既然我们已经创建出了不少的测试用例(多为基本的测试用例),那现在我们就可以开始介绍渗透测试部分的内容了。这部分操作还是比较简单的,大致分为如下几步:
1. 下载apr、apr-utils、nghttpd2、pcre-8和Apache httpd 2.4.25;
2. 安装下列依赖组件:
a) sudo apt install pkg-config
b) sudo apt install libssl-dev
3. 修复Apache httpd;
4. 用正确的环境变量和安装路径进行编译;
安装完上面这些依赖组件并配置好Apache服务器之后,我们就可以准备开始对Apache httpd进行模糊测试了。你可以从下面这段演示视频中看到,我们只需要对测试用例进行小小的改进就能够迅速导致程序出现崩溃:
需要注意的是,在上面这个演示视频中我小小地作弊了一手,因为我已经导入了一个我确定能够迅速引发程序出现崩溃的测试用例。在检测的过程中,我使用了honggfuzz、radamsa和AFL。
程序崩溃
重要的事情放在第一位。当我们得到一个能够引发程序发生崩溃的测试用例之后,我们必须要确定这个测试用例的假阳性。测试情况请看下面的演示视频:
额…好像这个测试用例并不是很好用,发生了什么呢?
解决问题
我们需要测试下面几个因素…
首先,我们的渗透测试是在持久模式(Persistent Mode)下进行的:
这也就意味着,我们的测试用例的确让程序崩溃了,但有效的测试用例数量不多。在我们的测试过程中,__AFL_LOOP变量的值被设置成了大于9000(说实话这个值太大了)。这个值是AFL在重启整个过程之前所需要运行迭代模糊测试的次数。所以最终AFL所发现的测试用例都将会在最差的环境下被运行。比如说:在前8999次所使用的测试用例都没有引发程序崩溃,而第9000次输入引发了程序崩溃。
第二个需要考虑的因素就是AFL报告的可靠性:
一般来说,导致结果可靠性较差的原因可能是代码中使用了随机值(或date函数),或者是使用了未初始化的内存。
第三点就是分配给我们模糊测试进程的内存大小:
在我们的测试环境中,由于我们使用了“-m none”参数,所以可用内存是无限的,但在其他情况下这将有可能导致堆栈溢出或访问未被分配的内存空间。
为了测试我们的第一种假设,我们还需要更多的测试用例。演示视频如下:
现在我们将注意力转移到第二个假设上。
稳定性和可靠性:
在进行模糊测试的过程中,我们会发现随着AFL引入越来越多的测试用例,其结果的可靠性也在不断下降,所以我们可以认为程序崩溃也许跟内存有关。为了测试我们是否使用了未经初始化的内存,我们可以使用下面这款名叫Valgrind的工具。
Valgrind是一款能够对软件进行动态分析的工具,它由一系列操作指令组成。在Debian平台上的安装命令如下:
sudo apt install valgrind
安装完成之后,我们需要通过Valgrind运行Apache服务器:
NO_FUZZ=1 valgrind -- /usr/local/apache-afl-persist/bin/httpd -X
在下面的演示视频中,我们对第二种假设进行了验证:
我们能确定的是,Apache httpd的确使用了未初始化的值,但令我不爽的是Apache并没有因此发生崩溃,所以我打算使用之前的Bash-fu Tao 2来迭代每一个测试用例,然后对Apache进行测试。
演示视频如下:
非常好,Apache httpd终于崩了。接下来,我们就可以对崩溃信息进行简单的分类了。
分类
现在我们需要对错误信息进行快速分析,然后找出那个引发崩溃的Header。
gdb + valgrind
首先,我们要在Shell窗口中运行下列命令:
NO_FUZZ=1 valgrind --vgdb-error=0 -- /usr/local/apache_afl_blogpost/bin/httpd -X
然后在另外一个Shell窗口中,我们需要发送下列输入信息来触发漏洞:
cat crashing-testcase.req | nc localhost 8080
最后在第三个Shell窗口中,我们可以运行gdb和valgrind的命令:
target remote | /usr/lib/valgrind/../../bin/vgdb
现在我们可以看看Apache的内部到底发生了什么,下图为Valgrind生成的错误报告:
从上图中可以看到,错误行为第1693行。由于变量s在递增的过程中没有进行检测,所以当它指向一个空值的时候便会导致程序崩溃。接下来请看下面这张截图,别忘了上图中的0x6e2990c和8749。
我们可以看到,变量conn的地址为0x6e2990c,分配地址为8192字节,但8749这个大小已经远远超出了8192。
rr-记录&重放框架
在下面这个视频中,我们演示了如何利用功能强大的rr框架来记录错误信息并重现程序崩溃:
总结
在这篇文章中,我们介绍了如何高效地从网上爬取测试用例,大家需要有些自信,虽然一个软件可能已经有很多人对它进行过渗透测试了,但我们仍然可以配合一些牛x的工具来找出其他人找不到的漏洞。这个漏洞的奖励金额为1500美金,我之后打算将这1500美金全部捐给某个可以教小孩子互联网技术的学校。