文/张小明
模糊测试(Fuzz Testing)一直被黑客们广泛使用,却很少应用于商业软件系统开发过程中。在互联网安全产业,如何让模糊测试重新焕发活力,是我一直在思考的问题。
概念及目的
在计算机领域,模糊测试是一个古老却容易被忽视的测试领域,它的诞生绝对早于真正意义上的自动化测试。甚至可以这么说:自从计算机允许用户输入,模糊测试便应运而生了。
所谓模糊测试,顾名思义可以理解成用一段非常规的字符串(或值)去替换正常的输入值,或者用非常规的、不合法的文件替换合法的文件输入,然后检测系统的行为是否正常、模块是否被破坏。虽然概念简单,但模糊测试的作用是非常巨大的,有时它能帮助发现严重的安全漏洞,这也是它深受广大黑客喜爱和认可的原因。
模糊测试在黑客界比较流行,凡是有输入点的地方总有黑客存在。基于对被攻击系统的学习和研究,黑客们会尝试有目的地设计出各种不同的随机输入值去攻击系统直至找到系统的漏洞。SQL Injection Attack便是黑客们常用的一个模糊测试的经典案例。
为了消除被攻击的风险,必须先发制人,在产品发布之前更多地发现产品的安全问题和漏洞便成了软件测试工作者关注的问题。
应用场景
我们的一款商业邮件服务器安全防护系统,目前用于测试的基本邮件库里大约有20万封测试邮件样本,扩展邮件库里大约有100万封。作为普通的压力测试之用,这些样本看起来已经足够多,而且邮件样本在不断更新,但从模糊测试的角度看,依然有以下不足点。
邮件库里的邮件样本是固定的,系统的输入点就是这些邮件的发送和接收,然后对邮件进行分析,固定的量不能覆盖更全、更完整的测试面。
在版本跟进过程中总是使用固定的样本不符合日新月异的测试需求,容易产生测试惰性。所谓惰性,就是固定的样本总是发现相同的问题,而这些问题早在之前就被发现了,再用同样的样本做压力测试很难发现新问题和新漏洞。
图1 模糊测试流程图
以上两个不足点的共同之处就在于样本是固定的。对此,我们的解决方案就是在压力测试中引入模糊测试,基于固有的测试样本基数模糊生成更多甚至成指数倍递增的模糊样本库,而且模糊的策略不同,模糊出来的样本也不尽相同,这便实现了我们测试的初衷——让样本更多、更动态。
在压力测试中如何进行模糊测试
压力测试中引入模糊模块在流程上是一个很简单的概念,就是将原始的输入样本进行一番模糊,生成更多的样本,在本文应用场景下,便是基于已有的邮件样本库然后模糊出来更大的样本库,示意流程如图1所示。
其中①便是压力测试系统新增的Fuzz模块,通过Fuzz模块扩容后的邮件样本库(扩容倍数会依据Fuzz策略不同而定)在压力测用例分发程序分发样本到指定的已安装了邮件安全产品的邮件服务器上去。
其中②表示分发程序根据样本分发策略具体进行分发的过程。
其中③表示新引入的自动化测试流程,基于Fuzz的压力测试后,产品需要进行安全漏洞和功能性验证,此处需要启动的自动化测试范围需要根据压力测试的策略和目标而定,最常见的比如:
图2 UI原型图
- 一般说来,模糊测试和压力测试中发现的问题都具备以下特点:
④在普通的功能性自动化测试验证压力测试结构之后,再有目的地做一些探索性的
测试。
图2中给出一个我使用WPF设计的UI原型图,配合上述流程图,可以看出四大块功能:Fuzz选项,被Fuzz的样本库配置,目标测试服务器配置,发送(启动测试)选项。
图3 Fuzz选项示意图
Fuzz功能设计
不同的项目和产品,不同的测试目标,Fuzz的策略和算法也不尽相同,但理念是一致的,那就是把正常的输入混淆以生成更多的输入值。比较通用的做法就是字节替换。字节替换意味通过将文件或者字符串中的字节替换成策略中的预设字节以达到Fuzz的效果, 如图3中给出了Fuzz选项示意。
其中:
Replace bytes:要替换为的字节。
Replace times:替换的次数,意味着样本里有多少字节会被替换成目标字节,如果选中Use Max file length, 意味着文件中所有字节都会被替换一边,假设文件长度是10个字节,那一份样本 Fuzz之后就是10份样本。
Target directory:Fuzz之后的样本保存路径。
Scope:Fuzz范围,因为有些文件太大,如果是针对所有字节全Fuzz 一边会造成测试数据过于庞大,所以一般针对大文件会指定Fuzz的字节范围。
图4 邮件样本Fuzz示意流程图
Culture text:针对全球化测试的需求,需要测试多语言文化下的系统功能,所以在Fuzz的同时并随机生成指定的语言文化字符串以应测试之需,更广地覆盖了测试范围。该部分会在后面部分再次探讨。
本文所用到的邮件样本库里的样本是以EML格式存在的 (EML格式是微软公司在Outlook中所使用的一种遵循RFC822及其后续扩展的文件格式, 并成为各类电子邮件软件的通用格式), 在Fuzz之前要进行EML文件解析, 一封邮件包含subject、 header、 body、attachment四个部分, 于是Fuzz的任务便拆分成了分别Fuzz邮件的四个部分,每一个部分分别Fuzz之后再与其他部分的结果排列组合成新的更多的样本。示意流程如图4所示。
下面是一段load Eml格式邮件的代码,实例化一封邮件,获得邮件各个组成部分的对象属性。
public static CDO.Message LoadMessageFromEml(string url)
{
ADODB.Stream adoStream = null;
adoStream = new ADODB.Stream();
adoStream.Open(Type.Missing, ADODB.ConnectModeEnum.adModeUnknown,
ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, string.Empty, string.Empty);
adoStream.LoadFromFile(url);
CDO.Message message = new CDO.Message();
message.DataSource.OpenObject(adoStream, “_stream”);
adoStream.Close();
return message;
}
强化Fuzz testing力度,引入Culture random test
商业产品尤其是针对国际市场的产品需要全球化本地化测试,这涉及多语言文化的数据测试,由于我们无法覆盖某语言文化下的全部字符,故使用随机生成测试数据的方式尽量弥补这一缺陷,这样可提高测试的覆盖面积。在模糊测试的环境里,随机的测试数据也属于Fuzz概念的一部分,所以除了上述普通的Fuzz算法策略,我们考虑结合多语言文化的随机测试,即:在Fuzz的基础上,给测试数据填充多语言文化的随机字符串,在Unicode编码规范里,UTF-8作为它的表现形式容纳了全世界所有的语言文化字符(当然,不是绝对的),而每一种不同的语言字符集被安置在不同的固定区域内,在固定的区域内随机出来的位置就是针对该语言文化下一个随机的字符,Unicode相关知识可参见
http://www.unicode.org/,这里不做赘述。
图5 发送选项的配置部分
细化Fuzz testing粒度,区分对待不同文件类型
邮件附件的Fuzz存在文件格式的问题,如果都按照文本文件Fuzz策略进行Fuzz,势必会导致很多格式化文件Fuzz达不到应有的效果,反而破坏了正确的文件格式,所以更细化的Fuzz应该因文件类型而异。当然,如果测试者的目标就是测试文件格式是否被兼容则另当别论。在本实战中,测试目的是Fuzz出来更多可用的格式正确的邮件。
常见的邮件格式化文件类型的附件有MS Office文件和Adobe文件,首先把常见的格式整理出来,然后通过相应的格式解释器去解析格式化文件中的具体内容,比如通过微软Office文件便提供Office编程模型解析相应的Office文件,然后针对解析出来的内容进行Fuzz,做到测试目标明确,节省测试精力。
发送选项及结果验证
Fuzz之后便是启动压力测试以及测试的验证部分,在文中的测试环境中,需要在不同的邮件服务器上分发测试数据,如图5所示是发送选项的配置部分。
图6 启动全程测试界面
其中,分发应用程序设置两种发送模式:Parallel, 并行模式,同时把所有的样本发送到所有可选的服务器上。Distributed, 分布式模式,为节约时间(经过Fuzz之后的样本会相对原来的量有指数倍的增长),把样本平均分配发送到不同的服务器上。
一个start!按钮带你启动全程测试:
然后就等着收获bug果实吧!
关于如何验证的流程,这里也做了创新,一般压力测试之后的验证会通过人工去检查程序或服务是否异常或是否产生内存泄漏,事实上这些都可以(应该)通过自动化测试去实现。而且,应该跑一轮功能性的自动化测试去验证在压力测试之后系统的基本功能是否还正常工作。最后再有目的地做探索性的测试。
作者张小明,英文名Sayid, 趋势科技南京研发中心软件测试工程师,专注于自动化测试相关的研究。