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

Luk*_*uke 21 security testing tdd unit-testing fuzzing

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

这带来了两个问题:

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

  2. 在标准TDD实践的什么时候应该包括这些测试,因为大部分安全性都是非功能性的.

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

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

谢谢!

编辑:

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

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

编辑2:

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

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

Dis*_*ned 10

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

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

未知的威胁

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

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

TDD将提供如下帮助:

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

可测试代码

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

专业测试

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

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

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

改进设计

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

  • 测试接收网络数据的组件,而无需通过网络实际发送.
  • 人们可以轻松地模拟对象,使其在攻击场景中可能出现意外/"不现实"的行为.
  • 单独测试组件.
  • 或者与任何所需的生产组件混合.

单元测试

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

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

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

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

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


ndp*_*ndp 5

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

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

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

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

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

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

希望这可以帮助