防守编程

时间:2008-08-09 17:02:57

标签: security defensive-programming

编写代码时,您是否有意识地进行防御性编程以确保高质量的程序并避免代码被恶意利用的可能性,例如:通过缓冲区溢出漏洞或代码注入?

您的代码始终适用的“最低”质量水平是什么?

14 个答案:

答案 0 :(得分:12)

在我的工作中,我们的代码必须是最高质量的 因此,我们专注于两个主要方面:

  1. 测试
  2. 代码评论
  3. 那些把钱带回家。

答案 1 :(得分:4)

与abyx类似,在我开发的团队中,开发人员总是使用单元测试和代码审查。除此之外,我还打算确保我不包含人们可能使用的代码 - 我倾向于仅为手头的对象所需的基本方法集编写代码正如已经规定的那样。我发现合并可能永远不会使用但提供功能的方法可能会无意中将“后门”或意外/意外使用引入系统。

稍后再回过头来介绍一些方法,属性和属性要比预期可能永远不会出现的内容要容易得多。

答案 2 :(得分:1)

我建议人们编写代码,这些代码在开发环境中是法西斯主义,在生产中是仁慈的。

在开发过程中,您希望尽早捕获错误的数据/逻辑/代码,以防止问题被忽视或导致以后出现根本原因难以跟踪的问题。

在生产中尽可能优雅地处理问题。如果某些内容确实是不可恢复的错误,则处理它并将该信息提供给用户。

这里的示例是我们的规范化矢量的代码。如果你在开发过程中输入坏数据就会尖叫,在生产中它会返回一个安全值。

inline const Vector3 Normalize( Vector3arg vec )
{
    const float len = Length(vec);
    ASSERTMSG(len > 0.0f "Invalid Normalization");
    return len == 0.0f ? vec : vec / len;
}

答案 3 :(得分:1)

我建议对进入“组件”或框架的数据采取防御措施。在“组件”或框架内,人们应该认为数据是“正确的”。

这样想。由呼叫者提供正确的参数,否则所有功能和方法都必须检查每个输入参数。但是,如果仅对呼叫者进行检查,则只需要进行一次检查。因此,参数应该是“正确的”,因此可以传递到较低级别。

  1. 始终检查来自外部来源,用户等的数据
  2. “组件”或框架应始终检查接听电话。
  3. 如果有错误,并且在通话中使用了错误的值。什么是正确的东西todo?只有一个迹象表明程序正在处理的“数据”是错误的,有些像ASSERTS,但其他人想要使用高级错误报告和可能的错误恢复。在任何情况下都会发现数据有问题,在极少数情况下继续处理它是件好事。 (注意,如果服务器至少不死,那就好了)

    从卫星发送的图像可能是尝试高级错误恢复的情况......从互联网上下载的图像,以便为...设置错误图标。

答案 4 :(得分:0)

使用测试驱动开发肯定有帮助。您一次编写一个组件,然后在编写代码之前枚举输入的所有可能情况(通过测试)。这可以确保您已覆盖所有基础,并且没有编写任何人将使用但可能会破坏的 代码。

虽然我没有做任何正式的事情,但我通常会花一些时间查看每个班级并确保:

  1. 如果他们处于有效状态,表明他们处于有效状态
  2. 无法在无效状态下构建它们
  3. 在特殊情况下,他们会尽可能优雅地失败(通常这是一次清理和抛弃)

答案 5 :(得分:0)

简单回答:取决于。 过多的防御性编码可能会导致严重的性能问题。

答案 6 :(得分:0)

根据我的经验,积极采用防御性编程并不一定意味着您最终会提高代码质量。不要误解我的意思,你需要采取防御性程序来捕捉用户遇到的各种问题 - 当程序崩溃时用户不喜欢它 - 但这不太可能使代码更容易维护,测试等。

几年前,我们制定了在我们软件的各个层面使用断言的政策,以及单元测试,代码审查等以及我们现有的应用测试套件 - 对质量产生了重大的积极影响。我们的代码。

答案 7 :(得分:0)

取决于。

如果我真的正在为自己使用某些东西,那么我会写出我不必考虑的最佳代码。让编译器成为我的警告等朋友,但我不会自动为它制作类型。

使用代码的可能性越大,即使偶尔也会增加检查级别。

  • 最小幻数
  • 更好的变量名称
  • 完全检查&定义的数组/字符串长度
  • 按合同断言编程
  • 空值检查
  • 例外(取决于代码的上下文)
  • 基本解释性评论
  • 可访问的使用文档(如果是perl等)

答案 8 :(得分:0)

我将对防御性编程采用不同的定义,就像Josh Bloch提出的 Effective Java 所倡导的那样。在本书中,他讨论了如何处理调用者传递给你的代码的可变对象(例如,在setter中),以及传递给调用者的可变对象(例如,在getter中)。

  • 对于setter,请确保克隆任何可变对象,并存储克隆。这样,调用者就无法在事件发生之后更改传入的对象来破坏程序的不变量。
  • 对于getter,如果接口允许,则返回内部数据的不可变视图;或者返回内部数据的克隆。
  • 当使用内部数据调用用户提供的回调时,请根据需要发送不可变视图或克隆,除非您打算使用回调来更改数据,在这种情况下,您必须在事后验证它。

带回家的消息是确保没有外部代码可以保存您在内部使用的任何可变对象的别名,以便您可以维护不变量。

答案 9 :(得分:0)

我总是努力防止像注射攻击这样的事情。但是,当您在内部Intranet站点上工作时,大多数安全功能都会像浪费精力一样。我仍然会这样做,也许还不行。

答案 10 :(得分:0)

嗯,有一套安全的最佳做法。至少对于数据库应用程序,您需要注意SQL注入。

散列密码,加密连接字符串等其他内容也是标准。

从现在开始,这取决于实际应用。

幸运的是,如果你正在使用.Net这样的框架,内置了很多安全保护。

答案 11 :(得分:0)

你必须总是以防御性方式进行编程我会说即使对于内部应用程序,只是因为用户可以通过纯粹的运气写出破坏你的应用程序的东西。当然,你可能不必担心试图欺骗你的钱,但仍然。始终采取防御措施,并假设应用程序将失败。

答案 12 :(得分:0)

Java,签名JAR和JAAS。

Java以防止缓冲区溢出和指针/堆栈攻击漏洞。

不要使用JNI。 (Java Native Interface)它向您公开DLL /共享库。

签名JAR以阻止类加载成为安全问题。

JAAS可以让你的应用程序不信任任何人,甚至是自己。

J2EE(基本上有限)内置了对基于角色的安全性的支持。

其中一些有一些开销,但安全漏洞消失了。

答案 13 :(得分:0)

我非常认为正确的编程可以防范这些风险。避免弃用功能的事情(至少在Microsoft C ++库中)由于安全漏洞而经常被弃用,并验证跨越外部边界的所有内容。

仅从代码中调用的函数不需要过多的参数验证,因为您控制调用者,即没有跨越外部边界。其他人的代码调用的函数应该假设传入的参数在某些时候是无效的和/或恶意的。

我处理暴露函数的方法是简单地崩溃,如果可能的话,使用有用的消息。如果调用者无法正确获取参数,则问题出在他们的代码中,他们应该修复它,而不是你。 (显然,您已经为您的函数提供了文档,因为它已经公开了。)

如果您的应用程序能够提升当前用户,则代码注入只是一个问题。如果进程可以将代码注入到您的应用程序中,那么它可以轻松地将代码写入内存并执行它。无法获得对系统代码的完全访问代码注入攻击毫无意义。 (这就是为什么管理员使用的应用程序不应该被较小的用户写入的原因。)