因此,我们专注于两个主要方面:测试中代码审查那些把钱带回家。与abyx相似,我在开发团队中始终使用单元测试和代"/>

关于安全性:防御性编程

关于安全性:防御性编程

Defensive programming

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

您将始终对代码应用的"最低"质量级别是多少?


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

  • 测试中
  • 代码审查
  • 那些把钱带回家。


    与abyx相似,我在开发团队中始终使用单元测试和代码审查。除此之外,我还旨在确保不包含人们可能会使用的代码-我倾向于只为手边的对象按照规定运行所需的基本方法集编写代码。 。我发现,合并可能永远不会使用但提供功能的方法可能会无意中将"后门"或意外/意外的使用引入系统。

    稍后再回过头来介绍要问的方法,属性和属性,而不是预料到永远不会出现的事情,要容易得多。


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

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

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

    例如,这是我们的标准化向量的代码。如果您在开发中提供错误的数据,它将大声疾呼,在生产中,它将返回安全值。

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

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

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

  • 始终检查来自外部来源,用户等的数据
  • "组件"或框架应始终检查传入的调用。
  • 如果有错误,并且在调用中使用了错误的值。真正正确的做法是什么?一个仅表明该程序正在处理的"数据"是错误的,一些表明像ASSERTS,但其他一些则希望使用高级错误报告和可能的错误恢复。无论如何,发现数据都是有缺陷的,在少数情况下,最好继续进行处理。 (请注意,如果服务器至少不死,那就很好了)

    从卫星发送的图像可能是尝试在...上进行高级错误恢复的情况...从互联网下载的图像会为...显示错误图标


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


    Java,签名JAR和JAAS。

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

    不要使用JNI。 (Java本机接口),它使您可以使用DLL /共享库。

    已签署JAR来停止类加载是一个安全问题。

    JAAS可以让您的应用程序不信任任何人,甚至不信任任何人。

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

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


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

    几年前,我们制定了在软件的所有级别上使用断言的策略,并且这连同单元测试,代码审查等,以及我们现有的应用程序测试套件,对代码质量产生了显着的积极影响。


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

    仅从代码中调用的函数不需要过多的参数验证,因为您可以控制调用者,也就是说,不会跨越外部边界。他人代码调用的函数应假定传入的参数在某些时候将是无效的和/或恶意的。

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

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


    我将采用防御性编程的不同定义,这是乔什·布洛赫(Josh Bloch)的有效Java所倡导的定义。在这本书中,他讨论了如何处理调用者传递给您的代码的可变对象(例如,在setter中),以及如何传递给调用者的可变对象(例如,在getter中)。

    • 对于setter,请确保克隆所有可变对象,然后存储克隆。这样,在事实破坏程序的不变式之后,调用者就无法更改传入的对象。
    • 对于getter,如果接口允许,则返回内部数据的不变视图;否则,返回true。否则返回内部数据的克隆。
    • 当使用内部数据调用用户提供的回调时,请视情况发送不可变的视图或克隆,除非您打算让回调更改数据,在这种情况下,您必须在事后对其进行验证。

    提示信息是确保没有任何外部代码可以为您在内部使用的任何可变对象保留别名,以便您可以维护不变量。


    这取决于。

    如果我真的是在为自己使用而乱搞一些东西,那么我将编写我不必考虑的最佳代码。让编译器成为警告等的朋友。但是我不会自动为它创建类型。

    使用代码的可能性更高,即使是偶尔,我也会提高检查级别。

    • 最小魔数
    • 更好的变量名
    • 全面检查和定义的数组/字符串长度
    • 通过合同声明进行编程
    • 空值检查
    • 异常(取决于代码的上下文)
    • 基本的解释性意见
    • 可访问的使用文档(如果有perl等)

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

    尽管我没有做任何正式的事情,但我通常会花一些时间看每节课,并确保:

  • 如果它们处于有效状态,则它们保持有效状态
  • 没有办法在无效状态下构造它们
  • 在特殊情况下,它们将尽可能正常地失败(通常是清理并抛出)

  • 我想说,即使对于内部应用程序,您也必须始终进行防御性编程,这仅仅是因为用户可以凭借纯粹的运气编写一些破坏您应用程序的内容。当然,您可能不必担心要用钱欺骗您,但仍然可以。始终进行防御性编程,并假定该应用程序将失败。


    好吧,有一些关于安全性的最佳实践。至少,对于数据库应用程序,您需要当心SQL注入。

    其他诸如哈希密码,加密连接字符串等的内容也是标准的。

    从这里开始,它取决于实际应用。

    幸运的是,如果您使用的是.Net等框架,则会内置许多安全保护。


    我一直努力防止注入攻击之类的事情。但是,当您在内部Intranet站点上工作时,大多数安全功能似乎都在浪费精力。我仍然这样做,也许不是很好。


    推荐阅读