【问题标题】:How can you ensure secure coding with Test Driven Development?您如何通过测试驱动开发确保安全编码?
【发布时间】:2011-07-19 08:35:35
【问题描述】:

我一直在跟上最新趋势,即测试驱动开发 (TDD)。我所做的大部分开发都是使用 C 或 C++ 进行的。让我感到震惊的是,常见的 TDD 实践和常见的安全编码实践之间存在非常明显的冲突。 TDD 的核心是告诉你,你不应该为没有失败测试的东西编写新代码。对我来说,这意味着我不应该编写安全代码,除非我有单元测试来查看我的代码是否安全。

这带来了两个问题:

  1. 如何有效地编写单元测试来测试缓冲区溢出、堆栈溢出、堆溢出、数组索引错误、格式字符串错误、ANSI、Unicode 和 MBCS 字符串大小不匹配、安全字符串处理(来自 Howard 和LeBlanc 的“编写安全代码”)?

  2. 在标准 TDD 实践中应该在什么时候包含这些测试,因为大部分安全性都不起作用。

令人惊讶的是,我发现讨论 TDD 和安全性的研究很少。我遇到的大部分是 TDD 论文,它们在非常高的层次上提到 TDD 将“使您的代码更安全”。

我正在寻找上述问题的任何直接答案,任何与此相关的研究(我已经看过但没有找到太多),或者 TDD 大师居住的任何地方,以便我可以去敲他们的门(虚拟),看看他们是否有任何好的答案。

谢谢!

编辑:

Fuzzing 的话题出现了,我认为这是解决这个问题的一个很好的方法(一般来说)。这就提出了一个问题:模糊测试是否适合 TDD?模糊测试在 TDD 流程中的哪些方面适用?

我也想到了参数化单元测试(可能是自动化的)。这可能是一种在测试过程早期获得类似模糊结果的方法。我也不确定它在 TDD 中的确切位置。

编辑 2:

到目前为止,感谢大家的回答。在这一点上,我对如何利用参数化测试作为函数的伪模糊器非常感兴趣。但是,我们如何确定要编写哪些测试来测试安全性?我们如何确保我们充分覆盖了攻击空间?

软件安全中的一个众所周知的问题是,如果您防范 5 种攻击场景,攻击者只会寻找并使用第 6 次攻击。这是一个非常困难的猫捉老鼠游戏。 TDD 对我们有什么好处吗?

【问题讨论】:

  • 如果您有足够的决心应用 TDD,那么您也可能有足够的决心应用相对轻量级的形式化方法,其中“测试”可以有效地测试是否存在 1 中列出的一些不需要的行为。您可以用不同的“测试”概念来保持 TDD 哲学。例如。 frama-c.com(披露:我在做这个)
  • 我当时没有这个例子,但现在在这里:blog.frama-c.com/index.php?post/2011/04/05/QuickLZ-1 这是一个后验验证的例子。如果您在编码之前,在单元级别以及库级别设计验证策略,我想您将接近 TDD。

标签: security unit-testing testing tdd fuzzing


【解决方案1】:

Fuzzing 的话题出现了,我认为这是一个很好的方法 这个问题(一般来说)。这就提出了一个问题:Fuzzing 是否适合 进入TDD?模糊测试在 TDD 流程中的哪些方面适用?

我相信它可能非常适合!有像american fuzzy lop 这样的模糊器可以编写脚本并自行适应 I/O 格式的修改。在这种特殊情况下,您可以integrate it with Travis CI,存储您使用的输入测试用例并针对这些测试用例运行回归测试。

如果您对 cme​​ts 中的详细信息有任何疑问,我可能会扩展此答案。

【讨论】:

    【解决方案2】:

    是的,TDD 是一种有助于确保安全编码的工具/技术。

    但就像这个行业的所有事情一样:假设它是一颗银弹,你会在自己的脚上开枪。

    未知威胁

    正如您在编辑 2 中指出的那样:“您可以防御 5 种攻击场景,攻击者只会寻找并使用第 6 次攻击”。 TDD不会保护您免受未知威胁。就其本质而言,您必须首先知道要测试什么才能编写测试。

    因此假设发现了 6 号威胁(希望不是由于违规,而是内部由于另一种工具/技术试图寻找潜在的攻击媒介)。

    TDD 的帮助如下:

    • 可以编写测试来验证威胁。
    • 可以实施解决方案来阻止威胁,并迅速确认其正在发挥作用。
    • 更重要的是,如果所有其他测试仍然通过,您可以快速验证:
      • 所有其他安全措施仍然正常运行。
      • 所有其他功能仍然正常运行。
    • 基本上,TDD 有助于缩短从发现威胁到提供解决方案的时间。
    • TDD 还提供了对新版本正确运行的高度信心。

    可测试代码

    我读到 TDD 经常被误解为一种测试方法,而实际上它更像是一种设计方法。 TDD 改进了代码的设计,使其更可测试

    专业测试

    测试用例的一个重要特性是它们能够在没有副作用的情况下运行。这意味着您可以按任意顺序、任意次数运行测试,并且它们永远不会失败。 结果,纯粹由于可测试性,系统的许多其他方面变得更容易测试。例如:性能、内存利用率。

    此测试通常通过对整个测试套件运行特殊检查来实现 - 不会直接影响套件本身。

    类似的安全测试模块可以覆盖测试套件并查找已知的安全问题,例如内存中的安全数据、缓冲区溢出或任何已知的新攻击向量。这样的覆盖将具有一定程度的置信度,因为它已经检查了系统的所有已知功能

    改进的设计

    作为 TDD 的副作用而产生的关键设计改进之一是显式依赖。许多系统都受到隐含或派生依赖的影响。这些将使测试几乎不可能。因此,TDD 设计往往在在正确的地方更加模块化。从安全角度来看,这允许您执行以下操作:

    • 测试无需通过网络实际发送即可接收网络数据的组件。
    • 可以轻松地模拟对象以在攻击场景中可能发生的意外/“不切实际”的方式运行。
    • 单独测试组件。
    • 或与任何所需的生产组件组合。

    单元测试

    需要注意的一点是,TDD 倾向于高度本地化(单元测试)。因此,您可以轻松测试:

    • SecureZeroMemory() 会正确地从 RAM 中清除密码。
    • 或者GetSafeSQLParam() 可以正确防范 SQL 注入。

    但是,验证所有开发人员是否在每个需要的地方都使用了正确的方法变得更加困难。
    验证新 SQL 相关功能的测试将确认该功能有效 - 它可以与 GetSQLParam 的“安全”和“不安全”版本同样有效。

    因此,您不应忽视可用于“确保安全编码”的其他工具/技术。

    • 编码标准
    • 代码审查
    • 测试

    【讨论】:

    • 谢谢。我特别喜欢您关于通过其他流程(例如模糊测试)发现安全问题然后使用该场景作为单元测试的输入的想法。我绝对同意 TDD 不会成为确保代码安全的灵丹妙药。我只是想找到一种方法来构建 TDD 纯粹主义者会满意的安全性(因为您不应该在没有失败测试的情况下编写代码)。在进行某些类型的测试时,我仍然看到一些挑战,但这是我接下来要解决的问题。
    • 值得一提的是,TDD 主要用于:1) 鼓励低耦合,因为这使测试更容易,以及 2) 增加对重构没有破坏应用程序的信心,因为单元测试表明现有的预期仍在发挥作用。
    【解决方案3】:

    参数化测试

    虽然我们没有在我的工作中进行缓冲区溢出测试,但我们确实有模板测试的概念。这些测试被参数化以要求我们要测试的案例的特定数据。然后,我们使用元编程通过将每个案例的参数应用于模板来动态创建真实测试。这具有确定性的好处,并作为我们自动化测试套件的一部分运行。

    我的 TDD 实践

    我们在我的工作中进行验收测试驱动开发。我们的大多数测试恰好接近全栈功能测试。原因是我们发现测试和确保用户驱动行为的行为更有价值。我们使用参数化测试生成动态测试等技术,以最少的工作为我们提供更多的覆盖范围。我们为 ASCII 与 UTF8、API 约定和众所周知的变体测试执行此操作。

    【讨论】:

      【解决方案4】:

      我先回答你的第二个问题。是的,TDD 作品可以用于非功能性需求。事实上,通常是这样使用的。改进的模块化设计最常见的好处是非功能性的,但每个实践 TDD 的人都能看到。我使用 TDD 验证的其他示例:跨平台、跨数据库和性能。

      对于所有测试,您可能需要重新构建代码以使其可测试。这是 TDD 的最大影响之一——它确实改变了您构建代码的方式。起初,这似乎在扰乱设计,但您很快就会意识到可测试的设计更好。总之……

      字符串解释错误(Unicode 与 ANSI)特别适合使用 TDD 进行测试。列举坏和好的输入通常很简单,并断言它们的解释。您可能会发现您需要稍微重构代码以“使其可测试”;我的意思是提取隔离字符串特定代码的方法。

      对于缓冲区溢出,确保例程在给定过多数据时正确响应也很容易测试。只需编写一个测试并向他们发送太多数据。断言他们做了你所期望的。但是一些缓冲区溢出和堆栈溢出有点棘手。您需要能够使这些发生,但您还需要弄清楚如何检测它们是否发生。这可能就像分配一个带有额外字节的缓冲区并验证这些字节在测试期间不会改变一样简单......或者它可能是一些其他创造性的技术。

      不过,我不确定是否有一个简单的答案。测试需要创造力、纪律和承诺,但通常是值得的。

      • 隔离您需要测试的行为
      • 确保您可以检测到问题
      • 知道您希望在错误情况下发生什么
      • 编写测试并看到它失败

      希望对你有帮助

      【讨论】:

      • 我认为在理想世界中,这是一个很好的方法。然而,我在实践中发现,很难编写 1 个测试、2 个测试甚至 50 个测试来正确地适应所有潜在的攻击场景。这就是为什么公司经常使用诸如模糊测试之类的技术来对代码进行数百或数千次测试。但是,模糊测试也不直接适合 TDD(请参阅我对 Rook 回答的评论)。
      • 没有什么可以说单元测试不能生成大量的随机输入。抽象地回答是不可能的,但我会想象一些测试快乐路径和已知边界条件的案例,然后可能是另一个充实了一类安全问题的猴子测试。听起来那里有很棒的工具,那么挑战是否更早地整合这些工具?还是这些在开发过程中运行起来太尴尬了?
      • 在开发过程中运行模糊测试工具可能有点尴尬,但@Craig Young 提出了一个好主意。您可以在之后运行它们(或并行运行?),一旦发现故障,进行单元测试以解决相同的错误。
      【解决方案5】:

      TDD 是构建安全系统的最佳方式。 Microsoft 开发的所有软件都经过模糊处理,这可以说是发现的漏洞显着减少的第一大原因。为此,我强烈建议使用 Peach Framework。我个人使用 Peach 在发现缓冲区溢出方面取得了巨大成功。

      Peach pit 文件提供了一种描述应用程序使用的数据的方法。您可以选择要测试的接口。您的应用程序是否读取文件?它有一个开放的端口吗?在你告诉 peach 输入是什么样的以及如何与你的应用程序通信之后,你可以放松它,我知道所有令人讨厌的输入,让你的应用程序自己呕吐。

      为了让一切运行起来,peach 有一个很棒的testing harness,如果你的应用程序崩溃,peach 会知道,因为它附加了一个调试器。当您的应用程序崩溃时,peach 将重新启动它并继续测试。 Peach 可以对所有崩溃进行分类,并将核心转储与它用于崩溃应用程序的输入进行匹配。

      【讨论】:

      • 我喜欢你在这里的方向。 Fuzzing也是我最初的想法之一。下一个合乎逻辑的问题是,我们如何将模糊测试更完整地融入 TDD 过程?也就是说,TDD 说应该在编写代码之前编写测试。我们是否也应该在编写代码之前设置模糊测试并运行它?完成你的产品并将其“扔到栅栏上”给测试组进行模糊测试似乎是个坏主意。我们希望利用 TDD 的优势更早地发现错误。对此有什么想法吗?
      • @Luke 理想情况下,在 TDD 中,您的代码也有据可查。最重要的是流入您的程序的任何数据/所有数据结构。一旦你知道数据是什么样子,你就可以为它编写 xml 坑文件。有了文档,可以预先编写此类测试,然后可以在开发过程中用作构建验证工具。此模型特别适用于远程 API、服务甚至客户端。
      猜你喜欢
      • 1970-01-01
      • 2013-07-21
      • 2011-03-04
      • 1970-01-01
      • 2011-09-09
      • 2012-10-10
      • 1970-01-01
      • 1970-01-01
      • 2014-01-13
      相关资源
      最近更新 更多