模糊测试的目标是检测未知的漏洞或错误。模糊测试通过被模糊处理的应用程序中的意外或异常行为揭示潜在的错误,例如崩溃、无限循环或用户或开发人员可能认为“不良”的其他行为。它通常通过改变输入到程序中的输入来实现这一点,希望进一步覆盖代码,因此程序的每个角落和缝隙都可以暴露给这个任意输入。目标是声称给定的程序足够健壮,可以按预期执行或找到程序中的错误,以便开发人员可以修复它们。
过去,模糊测试主要由安全社区使用。今天,模糊测试的能力比以往任何时候都容易;因此,模糊测试不仅被安全研究人员广泛使用,而且被软件开发人员和计算机工程师广泛使用。Fuzzing 的流行来自于使用自动化过程的能力,无需付出太多努力就能发现手动代码审查中遗漏的错误。模糊测试应用程序可以保持运行 - 只需最少的交互 - 一次最多可运行数天。
模糊测试是如何工作的?
虽然模糊测试看起来像是暴力破解,但实际上远不止于此。有一些活动部件使其与众不同。此外,并不是所有的模糊器都是一样的。
模糊器的类型
模糊器有两种形式:哑模糊器和智能模糊器。最流行的模糊测试应用程序往往是智能模糊测试器。然而,对于哑巴和智能模糊器仍然有有效的用例。
哑模糊器
dumb fuzzer 为在应用程序上执行模糊测试提供了一种快速简便的解决方案。这些模糊器的主要驱动概念是他们正在模糊测试的程序缺乏上下文或状态。模糊器通常不知道程序是否处于执行状态,也不知道程序是否正确接收了输入。他们只知道两件事:
- 程序中输入了什么?
- 如果程序崩溃了?
鉴于这两个知识点,一个愚蠢的模糊器可以判断是否有一些随机输入输入到程序中导致它崩溃。或者,通过在向程序输入输入后分析程序的输出,可以使哑模糊器变得稍微聪明一些。这可能有助于找到不一定导致崩溃的其他问题,而是另一个意外操作。
愚蠢的模糊测试的缺点是缺乏对它所测试的程序的了解。这可能是一个问题的一个很好的例子是,如果输入的格式需要在特定的模板中,例如对于需要密钥、用户名或目录等参数的程序的某些配置文件。这对于一个愚蠢的模糊器来说可能是个问题,但是一个聪明的模糊器可以轻松解决这个问题!
智能模糊器
智能模糊器(或至少比基本的模糊器更智能)将允许开发人员或研究人员探索更多应用程序并可能发现以前未发现的错误。“智能”来自这些类型的模糊器中内置的一些通用智能。一些情报点可能包括:
- 输入格式是什么样的?
- 最后一次输入是否比之前的输入导致了更多的代码覆盖?
- 可以对输入进行哪些修改以探索进一步的代码覆盖率?
如果模糊器能够识别这三个因素,那么为应用程序生成的输入类型将针对特定应用程序进行更精心的策划,从而比愚蠢的模糊测试更快地发现错误。
通常,智能模糊器将使用不同类型的算法来生成这些任意输入。这与简单地使用绝对随机输入(例如从 /dev/urandom 读取)的愚蠢模糊器方法相反。一些方法包括:
模糊测试方法 | 描述 |
模板/语法模糊测试 |
|
引导模糊测试 |
|
基于突变的模糊测试 |
|
基于生成/进化的模糊测试 |
|
每种技术都有其优点和缺点,可能并不适合所有用例。模糊测试作为一个整体往往是一种“见机行事”的游戏,这意味着它是一个试穿多双鞋直到找到适合你的场景的过程。
出于本文的目的,我们将避免关注“愚蠢”的模糊器,而更多地关注智能模糊器的结构和操作。
模糊结构
模糊测试环境可能因所需的实施而异。在这篇文章中,我们将重点关注大多数智能模糊器的一般结构,并为模糊器的操作方式提供简单的视觉效果。
模糊测试的组件
要执行有效的模糊测试,您的模糊器必须能够执行一些不同的任务:
- 生成新的种子/测试用例
- 启动目标程序(通过线束或仅通过程序)
- 为目标程序提供一个测试用例
- 确定给定案例是否提供了新的代码覆盖率
- 变异/进化提供正回报的输入
- 检测程序是否崩溃或停止
当然,这个列表并不详尽。但是,这些属性允许模糊器高效执行。
模糊测试的一般流程
在大多数情况下,为了对应用程序进行模糊测试,您的模糊器将执行以下步骤:
- 读取模糊器用户提供的种子文件夹中的种子
- 用每个种子启动目标程序并比较哪些提供了较新的代码覆盖率
- 对于第一次迭代,它将是所有这些,因为没有用于比较的先前执行
- 对于提供较新代码覆盖率的每个测试用例,使用选定的变异方法对其进行更改。在执行基于语法/模板的模糊测试时,确保它符合模板。
- 将这些新测试用例中的每一个添加到种子/测试用例队列中,以便模糊测试应用程序执行
一般来说,模糊测试看起来像这样:
在该工作流程中,模糊测试应用程序将不断检查目标应用程序是否已崩溃。如果有,导致崩溃的输入将被重新定位到与其他种子分开的文件夹中;因此,用户知道是哪个输入导致了这种意外行为。
有了这些组件和程序,模糊测试应用程序现在只需要一种与目标应用程序交互的方法。但有时,并非所有输入都是直截了当的。例如,有时需要修改文件以更改程序的输入。其他情况可能包括非标准输入方法,例如通过套接字、通过库调用或可能通过一些交互式输入。无论哪种方式,通常最好的做法是使用线束与目标程序进行交互。
模糊线束
当您想到安全带时,您可能会想到登山扣、高空滑索和登山装备。然而,当涉及到模糊测试时,它们的工作方式有很大不同。开发了一个模糊测试工具来弥合模糊器期望输入发生的方式与输入在应用程序中实际发生的方式之间的差距。它通过携带来自模糊器的输入并将其正确地传递给模糊测试目标来实现这一点,以便目标可以像任何正常交互一样处理输入。
一些程序需要特定的方法来将输入输入到程序中。不幸的是,模糊器不可能是所有行业的专家。试图适应世界上所有类型的程序是不现实的。为了使模糊器更容易与目标程序对话,模糊器的用户需要创建一个工具。harness 只是将从模糊器输入的标准测试用例输入转换为目标应用程序可以理解的内容。这允许模糊测试应用程序根据它对输入的反应来确定进一步的操作。
在大多数情况下,这些是有效模糊测试的要素。精心设计以帮助模糊器与目标程序对话的线束与足够智能以根据目标程序生成测试用例的模糊器配对,将证明是一项极好的资产。
有效的模糊测试和交易工具
在以下部分中,我们将讨论有效模糊测试的一些关键要素以及一些流行的工具以及这些工具之间的一些比较。
模糊测试工具
对于大多数需要模糊测试功能的用户来说,没有必要重新造轮子。有很多免费的构建良好的工具,您可以使用它们来对特定目标进行模糊测试。此类免费和开源工具包括:
- American Fuzzy Lop (AFL)
- 库模糊器
- 红旗
- Boo Fuzz
- 毛毛虫
- 趣味Fuzz
如果希望对程序进行彻底的模糊测试,您可能需要考虑使用这些模糊测试器中的多个。这一点尤其明显,因为并非所有这些模糊器的工作方式都完全相同。正如我们将看到的,并非所有的模糊器都适用于每种语言。
请记住,上述模糊测试器列表并不详尽,让我们快速浏览一下 AFL、LibFuzzer 和 Fuzzili,以了解它们各自的不同之处。
澳式橄榄球联盟
根据官方描述,“American fuzzy lop (AFL) 是一种面向安全的模糊器,它采用一种新型的编译时检测和遗传算法来自动发现干净、有趣的测试用例,这些用例会触发目标二进制文件中的新内部状态。”
好处:
- 支持黑盒和白盒测试。(有或没有源代码)
- 支持扩展到您自己的实施需求
- 使用基因模糊测试技术
缺点:
- 不是多线程
- 不提供任何本地模糊网络协议的能力
库模糊器
LibFuzzer 是最流行的模糊测试工具之一,它是一种进程内、覆盖引导的模糊测试引擎。LibFuzzer 与被测库链接,通常通过模糊测试工具通过特定的模糊测试入口点将模糊输入输入到库中。顾名思义,这是一个专门设计用于模糊库功能而不是单个程序的模糊器。目前,如果你想模糊一个目标,所讨论的库必须能够用 Clang 编译,因为 LLVM 带有 Clang 编译器。
好处:
- Fuzzer 已经是编译器的一部分,可以更轻松地与任何项目集成
- 立即支持地址消毒剂
- AFL 仅在您检测应用程序时才有此功能(这就是 LibFuzzer 的工作方式)
- 覆盖引导的模糊测试
缺点:
- 无法开箱即用地执行黑盒测试(通常只有在您有源代码时才使用)
- 主要用于模糊共享库而不是独立的二进制文件
毛毛虫
这是另一个覆盖引导的模糊器;但是,此模糊器适用于 JavaScript 等动态语言解释器。模糊器的主要目标是在 JavaScript 引擎上执行模糊测试并允许适应特定的 JavaScript 实现。
好处:
- 为 JavaScript 精心策划
- 在生成测试用例期间使用的四个修改器选项
- 使用多线程
缺点:
- 只为 JavaScript 编写
您可以很容易地看出,每个模糊器都有可以使用和不能使用的特定情况。在您的程序中使用多个模糊器可以提供更好的整体代码覆盖率,而不是只使用一种类型的模糊器。例如,如果您使用 LibFuzzer 从源代码检测程序,然后使用 AFL,您将获得两全其美的效果,甚至可以在两个模糊器之间共享崩溃数据。
不过,关于不同的模糊器已经说得够多了。最终将帮助您决定选择哪种模糊器取决于目标应用程序。
模糊什么
在任意级别上,您可以对任何内容进行模糊测试。困难的部分是如何将您想要模糊测试的内容伪造成可以以编程方式传递给应用程序进行处理的输入。例如,假设您想要对消息传递应用程序进行模糊测试。在这个消息传递应用程序中,您希望将文本框作为目标,用户可以在其中键入消息。您将如何以编程方式创建可以将来自模糊测试框架的输入传递到文本框中的线束?
在某种程度上,这可能非常困难,并且可能会导致一些有趣的利用。这也是为什么利用是模糊测试中比较困难的部分之一。您不仅必须处理运行时问题,还必须将输入获取到您想要的位置。
选择目标应用程序时的一些注意事项是:
- 这个应用程序受欢迎吗?
- 如果是这样,您最终的模糊测试投资回报率可能会很低
- 这可能需要您针对程序中更深层次的内容进行模糊测试
- 这是什么类型的应用程序/库?
- 如果应用程序使用 GUI,您将如何从 harness 发送输入?
- 如果应用程序不使用 GUI,您如何对无法从命令行访问的输入进行模糊测试?
寻找模糊测试目标的另一种途径可能源于主要项目所依赖的公共库或依赖项。但是,这些库不像使用它的主库或程序那样频繁地进行模糊测试。对库或依赖项进行模糊测试可以发现以前未检测到的漏洞。(参见https://github.com/python-pillow/Pillow/issues/5544)
编写一个“好的”工具(又名 Fuzzing Target)
线束或模糊测试目标是将要执行的目标文件,是目标应用程序和模糊测试框架之间的有效桥梁。一个示例实现可能是一个 harness,它旨在与 LibFuzzer 一起工作,将从标准输入读取,将参数传递给库函数,然后将结果返回给被调用者。在这种情况下,输入将来自 LibFuzzer,当出现成功返回值时,LibFuzzer 知道一切顺利。
在大多数情况下,想法是尽可能多地执行此线束。这通常是通过使用分叉服务器或外部导出 (LibFuzzer) 形式的模糊测试框架来实现的。因此,在尝试确保我们的线束尽可能高效时需要考虑的一些注意事项是:
- 处理非标准/畸形输入的能力
- harness 不应退出或中止,除非绝对必要以允许进一步的代码覆盖
- “垃圾收集”任何线程或创建的子进程的能力
- 避免超过 n^2(最多 n^3)的任何复杂性
- 最后,保持模糊测试目标更窄以允许更具体的模糊测试
上述注意事项在很大程度上取决于您的模糊测试实施。请记住,这些是大多数模糊器遵循的一般意识形态。为了更广泛和详细地描述制作一个好的模糊测试目标,谷歌有一个专门用于教学模糊测试的存储库。可以在此处找到目标创建部分。
谁应该进行模糊测试?
由于易于部署和自动化,模糊测试在计算机科学和工程领域的各个团体中获得了更多的关注。虽然模糊测试是网络安全研究人员工具箱中的一个有效工具,但它也应该是软件开发人员工具箱中的一个重要工具。
模糊驱动开发
如果现在开始一个新的开发项目,并且没有将模糊测试纳入您的测试管道,那么您就会发现重要的错误!如果你没有见过测试驱动开发(TDD),它就是根据项目需求为给定项目开发测试用例的过程。这个想法是在达到每个需求里程碑时创建这些,而不是等到最后为给定项目构建所有测试用例。纯 TDD 的缺点是测试空间对于许多开发人员来说是多么不完整。
在大多数情况下,使用 TDD 的开发人员会创建一组预期的失败和预期的成功。然而,这些情况仅限于开发人员的知识和应用程序目的的上下文。开发人员只知道他们知道的,不知道他们不知道的。因此,虽然他们可能已经成功地测试了他们的程序或库的功能,但并非所有可能输入可能造成严重破坏的边缘情况都被击中。为了确保每个测试用例都被命中,重要的是不仅要使用 TDD,还要使用模糊驱动开发 (FDD)。
在 FDD 中,不需要被测试的候选人是项目需求或主要功能。有时,这可能只是一般功能,例如打开和解析开发人员想要测试该文件或代码段的稳健性的文件。无论如何,总体思路是:
- 在开发人员想要模糊测试的应用程序或库中找到目标位置
- 创建一个将输入馈送到目标的线束
- 运行模糊器!
- 利润?
这里的想法是,因为开发人员可以完全控制应用程序的工作方式,所以他们可以轻松地操纵和分离目标位置。此外,在模糊测试时拥有源代码允许对目标程序或库进行检测。Instrumentation 允许模糊测试框架的用户更好地跟踪某些输入到给定模糊测试目标所达到的代码覆盖率。拥有源代码的另一个好处是能够实现额外的模糊测试助手,例如地址清理器,可以帮助捕获不会导致应用程序崩溃的错误和其他漏洞。作为开发人员,这是一个很好的机会,可以在其他人之前找到导致应用程序中出现意外操作的输入。
假设发生了崩溃。在开发人员对崩溃进行分类后,这意味着它已位于崩溃和修复的位置,开发人员可以开始将此输入重新处理到他们的测试流程中。请记住,TDD 本身并不是坏事。然而,通过将 FDD 与其结合使用,软件开发人员可以通过回归测试的艺术为其代码的特定功能创建更健壮的单元测试。在这种情况下,回归测试只是一种确保先前导致崩溃的任何输入不会在项目生命周期的后期导致崩溃的一种方法。
从这往哪儿走
你应该从这篇文章中学到什么?首先,理解模糊测试不再只是安全研究人员的专利。软件开发人员、应用程序用户和安全爱好者可以不受限制地访问用于许多不同用例的无数不同的模糊测试实用程序。其次,无论是在开发运营管道中使用还是在闪亮的新无人机中寻找漏洞,模糊测试都是必须的,应该尽可能实施!无论您使用的是愚蠢的模糊器还是我们讨论的智能模糊器,模糊器的适用性和实用性都是无与伦比的。展望未来,看看您可以在项目中的哪些地方使用模糊器来帮助确保您的项目即使是最抽象的用户输入也是安全的。