如何抵抗恶意数据的攻击(二)
上次我们谈到Fuzz Testing是一种使用随机数据来发现软件中潜在的安全漏洞的有效测试方法。接下来,我将结合我们上次所提及的Bug来具体谈谈如何实现Fuzz Testing。
首先我们要做的是了解程序所期望的数据格式,是二进制数据、文本数据还是二者的混合?如果是一个文件,那么文件头、文件体以及文件的其它部分的格式是什么?程序在解析数据之前是否会做完整性校验以检查数据的合法性?
Fuzz Testing可以是完全随机地生成测试数据(我们称之为Generation),也可以是通过随机修改已有的合法数据来生成测试数据(我们称之为Mutation)。
很显然,在测试需要特定数据格式并会对输入数据做检查的程序时,Mutation比Generation更有效率,它避免出现大量产生的测试数据却无法真正测试到程序中解析数据的核心代码的情况。这是因为Generation生成的测试数据在大多数情况下可能均不符合特定数据的格式要求,所以大部分的测试时间只是重复地测试了程序中检查数据格式合法性的代码,从而造成程序中真正解析数据的核心代码无法或很少被测试到的情况。
相反地,Mutation可能会遗漏一些极少被调用的代码逻辑,从而无法发现潜在的能被攻击者所利用的漏洞。这是因为一般情况下Mutation只是对一个已有的合法数据中的某一或某些部分进行随机修改,这样就可能遗漏当测试数据中多个部分均为非法数据时才会重现的问题。
所以在很多情况下,我们会同时使用Generation和Mutation来进行测试。一般说来Generation比较简单,我们只需要写个程序来完全随机地生成测试数据即可。而Mutation则相对复杂一些,我们需要了解和分析要测试的程序数据格式,并准备一个合法的待用测试数据,然后再根据Fuzz Testing的测试设计选用一些方法来随机生成测试数据。以下是一些常用的随机生成测试数据的Mutation方法:
1. 将输入数据中任意部分修改为随机数据。
2. 在输入数据的任意处插入任意长度的随机数据。
3. 将输入数据中任意二个部分的数据进行交换。
4. 对输入数据中任意部分进行异或处理。
5. 将输入数据中表示字符串的零终止符替换为可打印字符。
6. 将输入数据中表示文件路径或URL的部分替换为非法文件路径和URL,或替换为包含文件路径或URL中非法字符的字符串。
7. 将输入数据中部分ASCII字符随机替换为Unicode字符,或反之。
8. 将输入数据中表示数值的数据替换为一个数值的边界值。
9. 将输入数据中表示数据长度或偏移量的数值数据替换为一个随机的数值,零或一个负数,或数值的边界值等。
让我们来看看如何利用Mutation针对上一次我们所提到的Bug来做Fuzz Testing的分析。在这个Bug里,应用程序完全没有检查EXIF中所存储的图像大小是否合法,而是根据读出来的数值直接解码图像内容,从而产生程序异常。通过我们上面所提到的Mutation方法(把图像中存储图像大小的部分替换为一个负数),是完全有可能在Fuzz Testing中发现这个Bug。
在知道如何生成Fuzz Testing所需的测试数据之后,接下来我们需要一个自动化工具来按照制定测试方案自动产生随机测试数据,并将测试数据自动输入到我们需要测试的程序中执行测试。
在测试过程中,我们需要一些监控方法来捕捉程序运行时的各种异常情况,比如程序崩溃、进程挂起,以及一些其它有用的信息,比如CPU的占用率、程序的输出等等。所以一般情况下,我们需要借助现有的调试手段来帮助我们来实时监控程序在测试运行时的各种状态和事件,并将所捕捉到的异常情况记录下来以便在测试结束后进行后期的Bug分析和重现。
最后补充说明一点,在Fuzz Testing的同时我们还可以利用代码覆盖(Code Coverage)检查工具来辅助我们检测在Fuzz Testing过程中哪些代码路径被执行过,哪些没有被执行。从而进一步地改进Fuzz Testing以提高测试的覆盖率。
根据上述的分析结果,我们就可以确定如何来设计自己的Fuzz Testing工具。当然,现在网上已经有很多很好的Fuzz Testing工具,你也可以根据自己的需求来选择一个适合的工具。(完)