如何通过测试驱动开发确保安全编码?

时间:2011-03-21 00:12:17

标签: security unit-testing testing tdd fuzzing

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

这提出了两个问题:

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

  2. 在标准TDD实践的哪个阶段应该包含这些测试,因为大部分安全性都是无效的。

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

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

    谢谢!

    编辑:

    Fuzzing的主题已经出现,我认为这是解决这个问题的一个很好的方法(总的来说)。这引出了一些问题:模糊测试是否适合TDD?在TDD过程中,模糊测试适合哪些?

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

    编辑2:

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

    软件安全性中一个众所周知的问题是,如果您防范5种攻击情形,攻击者只会寻找并使用第6次攻击。这是一个非常困难的猫捉老鼠游戏。 TDD是否对我们有任何优势?

5 个答案:

答案 0 :(得分:10)

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

但就像这个行业中的所有事情一样:假设它是一颗银弹,你会自己射击。

未知威胁

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

所以假设发现第6号威胁(希望不是因为违规,而是内部由于试图找到潜在攻击媒介的其他工具/技术)。

TDD将提供如下帮助:

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

可测试代码

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

专业测试

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

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

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

改进设计

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

  • 测试接收网络数据的组件,而无需通过网络实际发送。
  • 可以轻易地模拟对象,使其在攻击情形中可能出现意外/“不现实”的行为。
  • 单独测试组件。
  • 或任何所需的生产组件组合。

单元测试

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

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

但是,验证所有开发人员在每个需要的地方都使用了正确的方法变得更加困难 验证新的SQL相关功能的测试将确认该功能的工作原理 - 它与GetSQLParam的'安全'和'不安全'版本一样好。

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

  • 编码标准
  • 代码评论
  • 测试

答案 1 :(得分:5)

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

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

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

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

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

  • 隔离您需要测试的行为
  • 确保您可以检测到问题
  • 知道你想要为错误案例发生什么
  • 编写测试并看到它失败

希望这有帮助

答案 2 :(得分:4)

TDD是构建安全系统的最佳方式。微软开发的所有软件都是模糊的,这可以说是发现漏洞急剧减少的首要原因。我强烈建议您使用 Peach Framework 来实现此目的。我亲自使用Peach在寻找Buffer Overflows方面取得了巨大成功。

桃坑文件提供了一种描述应用程序使用的数据的方法。您可以选择要测试的界面。你的应用程序读取文件吗?它有开放端口吗?在你告诉桃子输入的样子以及如何与你的应用程序通信之后,你可以把它变得松散,我知道所有令人讨厌的输入,让你的应用程序全身心投入。

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

答案 3 :(得分:0)

参数化测试

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

我的TDD练习

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

答案 4 :(得分:0)

  

Fuzzing的主题已经出现,我认为这是一个很好的方法   这个问题(一般来说)。这引出了一些问题:模糊测试是否合适   进入TDD?在TDD过程中,模糊测试适合哪些?

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

如果您在评论中提出任何问题,我可以延长此答案。