许多开发人员都听说过,在产品开发过程的早期修复 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](0)=="b") {
if(argv[1](1)=="o") {
if(argv[1](2)=="o") {
if(argv[1](2)=="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 类别:
内存崩溃和安全漏洞: 许多重要的安全漏洞,如基于堆的缓冲区溢出,内存释放后使用,基于栈的缓冲区溢出,对安全性有重大影响,而 [未初始化的变量和整数溢出也会导致产品的安全性收到损害。
调度错误: 竞态条件、挂起、活锁。
断言: 通常可以在调试构建中找到,但有些也可以在生产环境中看到,比如在 Web 浏览器中。
性能错误: 内存泄漏,在长时间使用操作系统而不重新启动后,最终会影响磁盘操作系统,这仍然是目前几个操作系统的典型问题!
Web 和移动 GUI 安全漏洞: 跨站脚本(XSS)、SQL 注入、XPATH 注入、PHP 代码注入和 Shell 命令注入。
从本质上来讲,模糊测试可以视为将“/dev/random
”的输出管道连接到程序的输入。然而,根据对输入格式和目标的知识,存在不同程度的敏捷性,如下所述。
基于对目标的先验知识程度,我们可以区分不同的模糊测试方法如下:
Dumb: 换句话说,就是随机位翻转。
基于语法的生成和变异模糊测试: 在这里,测试人员编写一种语法,它表达一组生成规则,这些骨折与模糊测试目标所接受的单词大致相同。
推理: 在向目标提交每个输入之后,将获得新的知识并重新注入生成过程中。
基于语法的模糊测试的一个例子,其中生成的语法单词将成为输入。这方面的一个例子是使用 HTML 文件对浏览器进行模糊测试,如 Microsoft Internet Explorer。*
我们通常区分 黑盒模糊测试 和 灰盒模糊测试,前者假设不了解应用程序的内部工作原理,后者我们能够获取有关应用程序内部工作原理的一些知识(例如,汇编级上的基本块已由应用程序和依赖项 DLL/SO 执行)。
出于黑盒测试和近似推理的目的,来自 OUSPG 的 Radamsa 通常是最常用的转变器之一。与此同时,Michal Zalewski 编写的 American Fuzzy Lop,是第一个将灰盒模糊测试大众化的公共模糊测试工具之一。它的工作原理是使用通过处理输入而获得的知识,这些输入将部分地指导你,或者至少增加你关于如何生成下一个输入的知识。这通常是通过在运行时记录知识来实现,或者,例如通过在编译时添加检测工具来度量在汇编级别执行的基本块来实现。
取决于是否对代码库应用过任何安全分析或错误查找技术,投资回报率通常极具价值,不仅如美国计算机应急响应小组(US-Computer Emergency Readiness Team,US-CERT)所指出的那样,从查找错误的能力和修补错误的效率(从发现错误到发布补丁),而且还包括公司或实体的总体成本。当 Intel、Nvidia 和 Qualcomm 需要修补一个具有安全影响的架构缺陷时,它们所承受的成本、负面宣传以及对股价的影响就是后者的证明!
近年来,LLVM(一个专注于模块化、可重用的编译器和工具链技术的项目)的发展也使得模糊测试变得更加实惠。但是,不要低估生成单独的模糊测试的构建成本。 这些应该是独立于调试构建的附件编译配置文件,通常是在启用所有断言时。此外,也不要低估存储、计算和数据分析等元素的 基础设施和维护成本。
注意,当涉及到确认和修复应用程序中的所有 bug 时,模糊测试并非完美的解决方案!特别是,它通常具有突变性质,所以在传递条件方面,如果输入的一部分是应用于另一部分的操作的结果,例如哈希值,它通常很弱。在这种情况下,当前的解决方案包括手动限制分析,然后最终通过条件预处理器定义(例如,通过 #ifdef )来简化模糊测试构建中的此类代码。
通过模糊测试实现的错误查找能力也收到以下能力的限制:创建新输入、检测代码和库、在没有图形界面的情况下执行代码的相关部分,以及在每个状态下计算测试结果(这取决于你想要暴露的错误)。
模糊测试未来最后可能的发展方向包括以下内容:
持续集成模糊测试:为此,在任何开发人员的每次提交时,都需要重新编译、测试和模糊测试的目标,以尽可能缩减 bug 的生存期。
模糊测试播放器的生成:许多中等成熟度的公司需要熟练的开发人员来处理他们想要模糊测试的功能。为此,开发人员需要编写模糊测试的播放器来调用这些函数,并设置到达这些函数所需的状态,同时尊重这些函数的合同预期。成熟的下一个阶段是自动生成这种模糊测试播放器的能力。
基于模型的模糊测试:这包括以复杂语法的形式拥有或迭代地构建输入的模型。
符号执行:这涉及到将输入的某些部分标记为符号值,某些部分标记为具体值,然后计算到达每个基本块的方程,并使用约束求解器解决这些大型方程。这使你能够获得新的具体输入,用于测试函数的参数。
基于污点分析的模糊测试:这是专门针对某些“汇点函数”(sink functions)的,以便在变异时获得更高的精度。
就像在计算机安全研究和工程中一样,不要想当然地认为模糊测试可以帮助你识别产品中的所有 bug。我还建议你学习如何使用和优化静态分析检查器和符号执行工具。不要犹豫,向红队演练渗透测试和代码审计方面的专家寻求建议,以便在产品被恶意组织利用之前,确定产品中最严重的安全漏洞。
原文链接:
https://www.welcometothejungle.co/fr/articles/btc-fuzzing-bugs
活动推荐
9 月 21 日,GTLC 南京站将正式拉开帷幕。三星电子(中国)高级总监 & TGO 鲲鹏会会员 刘明、WIFI 万能钥匙事业群 CEO & TGO 鲲鹏会会员 万玉权等一批业界优秀的技术领导者,将悉数到场,分享领先的技术管理思考与理念,从而为南京本地及远道而来的技术人提供一次精彩的、深度的探讨学习及拓展人脉的平台或机会。识别下方二维码或点击「阅读原文」查看 GTLC 南京站相关内容,报名皆可送图书一本哦!