软件测试 | 什么是安全测试
测试应用的功能通常是很简单的 —— 我们遵循普通用户所采取的方法来使用它。如果我们不确定预期行为是什么,通常也会有某种方法能知道 —— 询问别人,阅读需求,使用我们的直觉。负面测试遵循一些从正面测试中自然而然直接地获取的原则。我们知道银行的“存款”不应该是负值;密码不应该是 1MB 的 JPEG 图片;电话号码中不应该包含字母。随着我们对应用进行测试并建立起正面的功能测试,建立反面测试就自然而然地成为下一步。那么这与安全测试有什么关系?
安全测试就是要提供证明表明,在面对敌意和恶意输入的时候,应用仍然能够充分地满足它的需求。
提供证据
在安全测试中,我们考虑无法接受的输入的全体集合 —— 无限集 —— 并重点关注那些很可能在我们软件的安全需求方面造成严重失效的输入子集 —— 仍然是无限集。我们需要确定这些安全需求是什么,并决定什么类型的测试能够证明这些需求得到满足。这并不简单,但是通过逻辑和勤奋,我们能够想产品所有者提供有用的证据。
我们证明安全满足需求的方式将与证明功能满足要求的方法相同。我们建立输入,确定预期结果,然后建立并运行测试来锻炼系统。以我们与不熟悉安全测试的测试人员交往的经历来看,第一步和最后一步是最难的。设计反安全的输入和对软件进行测试是最难做的事情。大多数时间,预期的结果相当简单。如果我询问产品经历:“有人能够在不登录的情况下下载敏感数据吗?”。通常他很容易就会说不。因此,提供证据过程中较难的部分是创造出可能会造成这种状况的输入,然后确定这种情况是否会发生。
满足需求
有关软件工程学的 ANSI/IEEE 标准 729 将“需求”定义为用户为了解决问题或达成目标所需要的条件或功能,或为了满足合同、标准、规范或其他正式起效的文档,系统所必须拥有或满足的条件或功能。在得知需求的情况喜爱,所有测试人员都进行测试直到满足需求。即使需求并不是以充斥着“该软件应当”语句的形式出现,软件测试人员也往往能够就正确的响应达成共识,然后以预期结果的形式将其整理到测试之中。
安全测试与功能测试相似,因为它同样也依赖于对“我们想要怎样的行为”的理解。当然也可以说,于功能测试相比,安全测试更加依赖于需求,因为它有更多可能的输入和输出可供筛选。在需求编写者的脑海里,安全行为的定义往往更加模糊,因为大多数软件都不是安全软件。该软件有一些其他方面的主要用途,而安全是一种必须存在的非功能性需求。因为对安全的关注不够,所以这方面的需求常常缺失或不完整。
充分地满足需求,这种想法如何?因为安全是一个不断发展的过程,而且因为安全通常不是我们的主要功能,所以我们并不总是因为更安全就去做某件事。真正的软件安全实际上指的是风险管理。我们确保软件的安全程度满足我们的业务需求。有时,安全纯粹主义者会认为软件不够安全。只要它能够满足业务所有者 —— 当这些所有者意识到风险并充分理解自己所承担的风险时 —— 那么这个软件就足够安全。安全测试提供了必要的证据和信息,以便业主就承担多少安全风险方面做出知情决策。
安全测试是老调重谈
安全是一段旅程,而不是目的地。我们永远也无法做到能够宣城软件已经安全而我们的任务已完成的地步。在执行功能测试时,我们通常使用指定的、可接受的输入,这些输入将产生已知的、预期的结果。在安全测试中,并不存在同样的有限性来约束我们的期望。
设想我们正在测试这样一种需求:"对于最大不超过 MAXINT 的正整数,convertInToRoman(int)函数将返回有效的罗马数字字符串“。如果我们只是在进行功能测试,我们将提供 5 作为输入并确保函数返回“V”。边界值测试将检查最大整数值,0,-1 等取值。我们会检查“-5”作为输入时程序给出正确的异常处理,并确保输出不会是“-V”,而是适当定义的错误响应。最后,异常测试将使用等价类来确保函数在输入为 3.42 时不会返回“III.IVII”作为输出,并能够在输入是“Fork”等奇怪字符串时给出适当的错误处理。
但是,安全测试则超出上述内容,它需要理解问题域并精心构造恶意输入,例如,罗马数字算法中不易处理的一类输入是由许多 9 和 4 组成的数字(比如,9494949494)。因为它需要递归或参考前一个罗马数字,这可能在软件中导致深堆栈或内存使用过量。这不仅仅是个边界条件。如果我们在功能测试基础上进行安全测试,则需要增加大量测试用例。这意味着必须做两件事来使之便于管理:缩小我们的重点以及自动化。
所有熟悉软件测试的人都理解边界值和等价类划分的概念。不必牵涉到太深的标准测试著作,让我们重洗回顾一下这两点,因为我们的许多 Web 安全测试将遵循同样的模型。如果你已经熟悉这些测试中的过程,你会发现很容易就能够利用它们来组织你的安全测试。
边界值
边界值获取给定的输入并非常仔细地围绕着它能够接受的边界值进行测试。例如,如果输入允许使用从 0 到 100(包含 0 和 100)的整数来表示百分百,那么我们可以生成下列边界值:-1,0,1,3,99,100,101。为了生成边界案例,我们重点关注给定范围中最大和最小的两个值(0 和 100)。对每个边界,我们使用边界值本身,边界值减 1 和边界值加 1.另外,我们选择一些位于中间的值,这些值的结果应该非常好。这是个基准案例。
等价类
在试图找出反面测试值时,我们知道不可接受的输入的集合是一个无限集。作为一种战略,我们从中抽样,而不是试图去测试大量的输入。我们将无限的集合拆分成有一些共同之处的组 —— 等价类,然后我们从每个组中选出几个具有代表性的样品。
继续考虑“边界值”一节中的例子,我们需要选出几种非法输入类并进行试验。我们可以选择这样的类:负数、非常大的正数、字母组成的字符串、小数以及一些有意义的特殊值,比如 MAXINT。通常,我们会从每个类中挑选出少量的数值(比如两个)并将其添加到我们的测试输入集。
安全类
“边界值”一节中的 7 个边界值,以及从“等价类”一节中大约 9 种等价类中每一种挑选两个值,将反面数据测试用例集合从无限多减少到 25 个。这是个很好的开始。现在我们开始基于常见攻击和漏洞添加安全测试用例。这就是如何将安全测试变为日常功能测试中简单而普通的一部分。我们选择具有安全意义的特殊边界值,以及具有安全意义的特殊等价类值,并将这些融入到我们的测试规划和计划策略过程中。
有几种得到普通认可的安全输入类:SQL 注入字符串,跨站式脚本字符串以及其他类的编码形式。例如,为了绕过某些应用的输入验证程序,你可以使用 Base-64 或 URL 方式将某些攻击字符串编码。现在,不同于边界值和其他等价类,这些安全类实际上是无限多的。因此,我们再次战略性地对其抽样,使其成为易于处理的集合。就编码而言,我们可以选择 3 或 4 种编码。这会使我们的测试集合增加到 3 或 4 倍,将 25 个值变为 75 或 100 个值。有些其他办法,系统进行某种编码时或者失败,或者成功。如果在你使用 URL 方法编码-1 时系统失败了,那么它很可能也会在你使用 URL 方法编码 101 时失败。因此,你或许可以选择使用 Base-64 来编码一些值,URL 来编码其他值,HTML 来编码其他值,并对其他一些值进行多重编码。这样,你能够覆盖这些编码,而且不必将测试用例数量增加到 4 倍。或许这个数量只倍增到 50 个测试用例。
现在,SQL 注入和跨站式脚本的攻击字符串就交给你了。你必须进行一些判断,并选择一个能够在自己所拥有的时间内完成的合理的子集。如果你所测试的系统部分易于自动化,那么你可以在每个类中完成许多的测试用例。如果你在进行手动测试,那么你或许应当获取一个不同攻击字符串的长列表,并在每次进行测试时尝试不同的字符串。这样,尽管你没有在每次测试运行时都测试每一条字符串,但是最终你将完成大量不同的案例。
评论