模糊测试:如何自动创建复杂的测试用例并发现未知错误

阅读数:946 2019 年 9 月 17 日 09:07

模糊测试:如何自动创建复杂的测试用例并发现未知错误

据 Wikipedia 介绍,模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是将自动或半自动生成的随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。模糊测试最早由威斯康星大学的 Barton Miller 于 1988 年提出。他们的工作不仅使用随机无结构的测试数据,还系统的利用了一系列的工具去分析不同平台上的各种软件,并对测试发现的错误进行了系统的分析。此外,他们还公开了源代码,测试流程以及原始结果数据。模糊测试工具主要分为两类,变异测试(mutation-based)以及生成测试(generation-based)。模糊测试可以被用作白盒,灰盒或黑盒测试。文件格式与网络协议是最常见的测试目标,但任何程序输入都可以作为测试对象。常见的输入有环境变量,鼠标和键盘事件以及 API 调用序列。甚至一些通常不被考虑成输入的对象也可以被测试,比如数据库中的数据或共享内存。对于安全相关的测试,那些跨越可信边界的数据是最有趣的。比如,模糊测试那些处理任意用户上传的文件的代码比测试解析服务器配置文件的代码更重要。因为服务器配置文件往往只能被有一定权限的用户修改。今天,我们翻译并分享了全球公认的世界级安全专家、模糊测试专家 Fabien Duchene 的文章 Fuzzing: How to Automatically Create Complex Test Cases and Uncover Unknown Bugs,以飨读者。

许多开发人员都听说过,在产品开发过程的早期修复 bug,要比在产品发布之后修复 bug 更划算、更高效。这是真的!许多研究表明,随着产品在软件开发生命周期(Software Development Life Cycle,SDLC)中的发展,修复安全漏洞的成本将呈指数级增长。2001 年, Soo Hoo 等人 计算出,在部署阶段修复一个 bug,要比在开发阶段进行修复要贵 100 倍。

技术娴熟的研究人员可以利用安全漏洞来制造系统越狱,这样就避免了必须使用应用商店来安装移动应用程序,而 Apple、Google 和 Samsung 等公司很大一部分收入都来自于应用商店!大家都还记得 2016 年 Samsung Galaxy Note S7 电池爆炸的事件吧?据估计,这事件让公司造成了高达 170 亿美元 的损失!

几年前,法国数据保护机构法国数据保护局(CNIL)对一家房屋协会处以 7.5 万欧元的罚款,理由是该协会的系统缺乏安全性。虽然对跨国公司来说,这笔罚款不算多;但对小企业来说却是一笔不小的罚款。现在,随着 GDPR 法律的推进,一家公司可以被处以相当于全球年收入 4% 的罚款!想象一下,欧盟委员会能从 Google 这样的互联网巨头那里拿到多少钱,要知道,Google 在 2018 年的收入可是高达 1362.2 亿美元 啊!

模糊测试的定义

你还记得吗?在 Windows 95 下,如果你将 Internet 上的一些文本复制粘贴到 Microsoft Word 文档中,进程 word.exe 就会崩溃?又或者,当 Visual Studio 在执行某些 gdi32 API 调用时会发生崩溃?仅仅因为产品没有经过未预料的输入测试,就浪费了这么多的时间,这一定是多么可怕的感觉啊!

测试提供了一系列可行的方法来检测实现错误。许多开发人员都熟悉测试的概念,主要是以功能测试的形式进行测试,例如单元测试,其中有一组固定的输入,然后用这些参数调用函数,并且使用一组单元测试作为回归套件。

另一方面,通过负面测试,你可以将重点放在生成和提交输入上,目的是触发错误,而不是评估函数与某些固定输入的一致性。模糊测试是一种特殊的负面测试类型,在这种测试中,你可以自动生成并评估异常输入,以便触发目标 bug 系列(或多个系列)的影响。它有时也被称为“软件酷刑行为”( Vuagnoux,2005 年),这一术语最初由 Barton Miller 创造(Barton 等人,1989 年;Forresteer、Miller,2000 年)。因为它涉及在测试条件下生成并向系统提交大量部分格式错误的输入,以期触发故障的影响。

模糊测试的用户

在硅谷,FAANG 使用模糊测试来确保其产品的健壮性和安全性,特别是搜索安全漏洞,换句话说,就是脆弱点。(译注:是指 Facebook、Apple、Amazon、Netflix 和 Google 的一线科技公司的首字母缩写。)任何开发足够复杂或关键产品的测试,无论是在处理关键数据方面,还是在具有关键影响或显著竞争优势的产品方面,都应该进行模糊测试,不仅要用于软件栈,而且还应该也要用于硬件栈。 实际上,忽略这样一个事实是不现实的:对于任何协议或数据处理,都应该进行模糊测试。例如,一些银行需要对 SWIFT 消息的输入格式进行模糊测试,一些汽车制造商需要对智能汽车中不同单元之间发送的消息进行模糊测试。

任何开发团队都应该在至少 80% 的核心功能(如音频、视频、网络和模式处理)上进行模糊测试,并把其作为最终单元测试的一种形式,尤其是那些特别容易出错的区域包括带有手写汇编部分的硬件加速路径、复杂的基于状态的操作和“面条式代码”。

译注:面条式代码(Spaghetti code)是软件工程中反面模式的一种,是指一个代码的控制结构复杂、混乱而难以理解,尤其是用了很多 GOTO、例外、线程、或其他无组织的分歧架构。其命名的原因是因为程序的流向就像一盘面一样的扭曲纠结。面条式代码的产生有许多原因,例如没有经验的程序员,及已经过长期频繁修改的复杂程序。结构化编程可避免面条式代码的出现。

模糊测试的概述

将模糊测试看做单元测试的最终形式是最好的方法!

模糊测试:如何自动创建复杂的测试用例并发现未知错误

让我们看一下这段 C 代码:

复制代码
int main(int argc, char** argv) {
if(argv 1 =="b") {
if(argv 1 =="o") {
if(argv 1 =="o") {
if(argv 1 =="m") {
assert(false); // to trigger a crash
}
return 0;
}

如果我们通过./pgm_name boom 调用此程序,它就会崩溃。

在确认目标的阶段,给定一个目标函数,模糊测试基本上会修复除一个缓冲区之外的所有参数,这个缓冲区通常会被攻击者“污染”(例如 httpd,它是通过 recv() 从用户接收参数)。在生成模糊测试数据的阶段,你将迭代该缓冲区参数的阈值。当与灰盒技术结合使用时,如本文稍后解释的边缘覆盖技术,模糊测试将发现 “b” 覆盖了输入 “a” 没有覆盖的边缘。然后它将保留 “b” 以备后用。在随后的生成模糊测试数据的步骤中,变异算子的一个例子是“附加一个字符”。这里的情况也是一样的:附加 “o” 将构造输入 “bo”,它涵盖了以前没有涵盖的边缘。因此,我们将保留此输入,直到模糊测试创建输入 “boom”,这将处罚一个失败,这里以断言的形式出现。这个输入 “boom” 将保留为崩溃输入,然后可以在你的测试套件中作为回归输入使用。

更形象地说,使用灰盒模糊测试技术,你可以自动生成输入,如此处所示,它是 GIF 渲染库的模糊测试。

模糊测试:如何自动创建复杂的测试用例并发现未知错误

通过模糊测试发现的错误类型

在传统上,模糊测试用于搜索内存崩溃的 bug。在开发现代应用程序时,我们通常感兴趣的不仅仅是安全漏洞。因此,将模糊测试技术与错误检测器结合起来,将扩展你的 bug 发现能力。

下面是一些可以通过结合模糊测试和测试结论相结合可以检测到的 bug 类别:

模糊测试的不同风格和巧妙之处

从本质上来讲,模糊测试可以视为将“/dev/random”的输出管道连接到程序的输入。然而,根据对输入格式和目标的知识,存在不同程度的敏捷性,如下所述。

模糊测试和输入格式的知识

基于对目标的先验知识程度,我们可以区分不同的模糊测试方法如下:

  • Dumb: 换句话说,就是随机位翻转。
  • 基于语法的生成和变异模糊测试: 在这里,测试人员编写一种语法,它表达一组生成规则,这些骨折与模糊测试目标所接受的单词大致相同。
  • 推理: 在向目标提交每个输入之后,将获得新的知识并重新注入生成过程中。

模糊测试:如何自动创建复杂的测试用例并发现未知错误

基于语法的模糊测试的一个例子,其中生成的语法单词将成为输入。这方面的一个例子是使用 HTML 文件对浏览器进行模糊测试,如 Microsoft Internet Explorer。

模糊测试和目标的知识

我们通常区分黑盒模糊测试灰盒模糊测试,前者假设不了解应用程序的内部工作原理,后者我们能够获取有关应用程序内部工作原理的一些知识(例如,汇编级上的基本块已由应用程序和依赖项 DLL/SO 执行)。

出于黑盒测试和近似推理的目的,来自 OUSPG 的 Radamsa 通常是最常用的转变器之一。与此同时,Michal Zalewski 编写的 American Fuzzy Lop ,是第一个将灰盒模糊测试大众化的公共模糊测试工具之一。它的工作原理是使用通过处理输入而获得的知识,这些输入将部分地指导你,或者至少增加你关于如何生成下一个输入的知识。这通常是通过在运行时记录知识来实现,或者,例如通过在编译时添加检测工具来度量在汇编级别执行的基本块来实现。

模糊测试和 Sanitizer Harness 的初始投资成本

取决于是否对代码库应用过任何安全分析或错误查找技术,投资回报率通常极具价值,不仅如美国计算机应急响应小组(US-Computer Emergency Readiness Team,US-CERT)所指出的那样,从查找错误的能力和修补错误的效率(从发现错误到发布补丁),而且还包括公司或实体的总体成本。当 Intel、Nvidia 和 Qualcomm 需要修补一个具有安全影响的架构缺陷时,它们所承受的成本、负面宣传以及对股价的影响就是后者的证明!

近年来, LLVM (一个专注于模块化、可重用的编译器和工具链技术的项目)的发展也使得模糊测试变得更加实惠。但是,不要低估生成单独的模糊测试的构建成本。 这些应该是独立于调试构建的附件编译配置文件,通常是在启用所有断言时。此外,也不要低估存储、计算和数据分析等元素的基础设施和维护成本

模糊测试的权衡

注意,当涉及到确认和修复应用程序中的所有 bug 时,模糊测试并非完美的解决方案!特别是,它通常具有突变性质,所以在传递条件方面,如果输入的一部分是应用于另一部分的操作的结果,例如哈希值,它通常很弱。在这种情况下,当前的解决方案包括手动限制分析,然后最终通过条件预处理器定义(例如,通过 #ifdef )来简化模糊测试构建中的此类代码。

通过模糊测试实现的错误查找能力也收到以下能力的限制:创建新输入、检测代码和库、在没有图形界面的情况下执行代码的相关部分,以及在每个状态下计算测试结果(这取决于你想要暴露的错误)。

模糊测试的未来:智能模糊测试

模糊测试未来最后可能的发展方向包括以下内容:

  • 持续集成模糊测试:为此,在任何开发人员的每次提交时,都需要重新编译、测试和模糊测试的目标,以尽可能缩减 bug 的生存期。
  • 模糊测试播放器的生成:许多中等成熟度的公司需要熟练的开发人员来处理他们想要模糊测试的功能。为此,开发人员需要编写模糊测试的播放器来调用这些函数,并设置到达这些函数所需的状态,同时尊重这些函数的合同预期。成熟的下一个阶段是自动生成这种模糊测试播放器的能力。
  • 基于模型的模糊测试:这包括以复杂语法的形式拥有或迭代地构建输入的模型。
  • 符号执行:这涉及到将输入的某些部分标记为符号值,某些部分标记为具体值,然后计算到达每个基本块的方程,并使用约束求解器解决这些大型方程。这使你能够获得新的具体输入,用于测试函数的参数。
  • 基于污点分析的模糊测试:这是专门针对某些“汇点函数”(sink functions)的,以便在变异时获得更高的精度。

就像在计算机安全研究和工程中一样,不要想当然地认为模糊测试可以帮助你识别产品中的所有 bug。我还建议你学习如何使用和优化静态分析检查器和符号执行工具。不要犹豫,向红队演练渗透测试和代码审计方面的专家寻求建议,以便在产品被恶意组织利用之前,确定产品中最严重的安全漏洞。

作者介绍:

Fabien Duchene 博士,公认的世界级信息安全专家、模糊测试专家,具有 16 年以上的国际大型安全经验,发现了数千个足以影响数十亿人的漏洞。他为 NASDAQ 的顶级公司和政府提供咨询和培训,同时也是 @Black-Hat、HITB、IEEE WCRE 的演讲者。

原文链接:

Fuzzing: How to Automatically Create Complex Test Cases and Uncover Unknown Bugs

评论

发布