软件安全的最大风险是检验工具及过程不透明的本质,以及不同的检验技术(例如自动化动态
测试)不能覆盖假阴性错误的潜在可能性。
尽管安全
软件开发生命周期(SDLC)有很多相关的最佳实践,但大多数组织依然有一种倾向,那就是主要依赖测试去构建安全的软件。当前的测试方法有一个最严重的副作用,即组织不太清楚哪些已经被其解决方案测试过,而(甚至更重要的是)还有哪些未被测试过。我们的研究表明,任何单一的自动化保证机制最多可以检验44%的安全需求。NIST 静态分析工具博览会发现,Tomcat中有26个已知的漏洞,但所有静态分析工具综合起来只报告了其中4个警告。因为依赖不透明的检验过程是一种普遍存在的习惯,甚至已经成为行业标准,因此许多组织在构建安全的软件时,满足于把测试作为最主要的手段。
举个例子,假设你雇用一家咨询公司为你的软件执行渗透测试。许多人将这种测试称为“黑盒”(基于同名的质保技术),测试人员没有详细的系统内部构件知识(比如系统代码)。执行测试之后,一成不变地生成一份报告,概括你应用中的几类漏洞。你修复了漏洞,然后提交应用做回归测试,下一份报告反馈说“已清除”——也就是说没有任何漏洞了。或者充其量仅仅告知你,你的应用在同一时间范围内不会被同样的测试人员以同样的方式攻破。但另一方面,它不会告诉你:
你的应用中还有哪些潜在的威胁?
你的应用中哪些威胁“其实不易受到攻击”?
你的应用中有哪些威胁未被测试人员评估?从运行期的角度来看,哪些威胁无法测试?
测试的时间及其他约束如何影响了结果的可靠性?例如,如果测试人员还有5天时间,他们还将执行哪些其他的
安全测试?
测试人员的技能水平有多高?你能否从不同的测试人员或者另一家咨询公司手中取得一组相同的结果?
以我们的经验来看,组织无法回答以上大多数问题。黑盒是两面的:一方面,测试人员不清楚应用的内部结构;而另一方面,申请测试的组织对自己软件的安全状况也缺乏了解。并不只是我们意识到了这个问题:Haroon Meer在44con上讨论了渗透测试的挑战。这些问题大多数都适用于任何形式的验证:自动化动态测试、自动化静态测试、手工渗透测试以及手工代码审查。实际上, 近期有一篇论文介绍了源代码审查中类似的挑战。
关于需求的实例
为了更好地说明这个问题,让我们看一些常见的高风险软件的安全需求,以及如何将常见的验证方法应用到这些需求上。
需求:使用安全的哈希算法(如SHA-2)和唯一的混淆值(salt value)去哈希(Hash)用户密码。多次迭代该算法。
在过去的一年里,LinkedIn、Last FM和Twitter发生了众所周知的密码泄露事件,对于此类缺陷,本条需求是具体地、合乎时宜的。
如何应用常见的验证方法:
自动化运行期测试:不可能访问已存的密码,因此无法使用此方法验证本需求
手工运行期测试:只有另一些开发导致已存密码转储时,才能使用此方法验证本需求。这并不是你所能控制的,因此你不能依靠运行期测试去验证本需求。
自动化静态分析:只有满足以下条件时,才可以用此方法验证本需求:
工具清楚身份认证是如何
工作的(例如,使用了标准的组件,就像Java Realms)
工具清楚应用程序使用了哪个特定的哈希算法
如果应用程序为每次哈希使用了唯一的混淆值,工具要清楚混淆算法和混淆值
实际上,认证有很多实现方法,指望静态分析方法全面地验证本需求是不切实际的。更为实际的方案是,使用工具简单地确认认证程序,并指出必须进行安全的哈希和混淆处理。另一个方案是,你来创建自定义规则,用以鉴定算法和哈希值,确认它们是否符合你专属的策略,尽管,在我们的经验中这种实践极为罕见。
手工代码审查:对于本需求,这是最可靠的常见验证方法。手工评估人员能够理解哪一段代码中发生了认证,验证哈希和混淆处理符合最佳实践。
SQL 注入是最具破坏性的应用漏洞之一。近期发现在
Ruby on Rails中有一个缺陷,在其技术栈上搭建的应用系统会受到SQL注入攻击。
如何应用常见的验证方法:
自动化运行期测试:虽然,运行期测试通过行为分析也许能够发现存在的SQL注入,但是,却不能证明没有SQL注入。因此,自动化运行期测试不能充分地验证本需求
手工运行期测试:与自动化运行期测试一样具有相同的局限性
自动化静态分析:通常能够验证本需求,特别是当你使用标准类库访问SQL数据库时。你是否将用户输入动态地拼接为SQL语句,还是使用正确地变量绑定,工具应该都可以分辨得出来。然而,这是有风险的,在以下场景中静态分析可能会漏掉SQL注入漏洞:
你在
数据库上使用存储过程,并且无法扫描数据库代码。在某些情况下,存储过程也易受到SQL注入
你使用了一种对象关系映射(ORM)类库,但你的静态分析工具不支持这种类库。对象关系映射也易受到注入。
你使用非标准的驱动或类库去连接数据库,并且驱动没有正确地实现常见地安全控制(比如预编译语句)
手工代码审查:与静态分析一样,手工代码审查能够确认没有SQL注入漏洞。然而,实际上产品应用中可能有几百或成千上万条SQL语句。手工审查每一条语句不仅非常耗时,而且容易出错。
需求:使用授权检查以确保用户无法查看其他用户的数据。
我们每年都能听到此类 漏洞新的事例。
如何应用常见的验证方法:
自动化运行期测试:通过访问两个不同用户数据的方式,使用一个用户的账号尝试访问另一个用户的数据,自动化工具能够在一定程度上完成本需求的测试。然而,这些工具不可能清楚一个用户账号的哪些数据是敏感的,也不了解把参数“data=account1”修改为“data=account2”表示违反了授权。
手工运行期测试:通常情况下,手工运行期测试是发现这类漏洞最有效的方法,因为人可以拥有必需的领域知识以探明这类攻击的位置。然而,在有些情况下,运行期测试人员可能无法全面掌握发现这类缺陷所必需的一切信息。例如,如果附加一个类似于“admin=true”的隐藏参数,使你可以不需授权检查就能访问其他用户的数据。
自动化静态分析:如果没有规则的定制,自动化工具通常发现不了这种类型的漏洞,因为它需要对领域的理解能力。例如,静态分析工具不清楚“data”参数表示条件信息,需要授权检查。
手工代码审查:手工代码审查能够揭露缺失授权的实体(译者注,比如代码),这是使用运行期测试难以发现的,比如添加一个“admin=true”的参数的影响。但是,实际上采用这种方式去验证是否做了授权检查费时费力。一处授权检查可能出现在许多不同部分的代码中,所以手工审查人员可能需要从头到尾追踪数条不同的执行路径,以检测是否做了授权。
对你的影响
验证的不透明的本质,意味着有效的软件安全需求的管理是必要的。对于已列出的需求,测试人员即可以明确他们已经评估一条具体的需求,也可以明确他们所用到的技术。评论家提出,渗透测试不应该遵循一张“类似于审计的检查表”,因为没有检查表可以覆盖模糊的范围和特定领域的漏洞。但是,要灵活地找到独特的问题,就不可避免地要确定已经充分理解了需求。这种情况与标准的软件质量保证(QA)非常相似:好的质保测试人员即能够验证功能需求,也能够思考盒子的边界,想办法去破坏功能。如果只是简单、盲目地测试,报告一些与功能需求无关的缺陷,就会显著降低质量保证的效用。那么为什么要接受较低标准的安全测试呢?
在你执行下一次安全验证活动之前,确保你有软件安全需求可用于衡量,并且你要明确属于验证范围内的需求。如果你雇佣手工渗透测试人员或源代码审查人员,他们就可以相对轻松地确定哪些需求是由他们来测试的。如果你使用某种自动化工具或服务,合作的供应商会表明,哪些需求无法用他们的工具或服务可靠地测试。你的测试人员、产品或服务不可能保证完全没有假阴性错误(例如,保证你的应用中不会受到SQL注入攻击),但同时也要理解,对它们做测试能够大大有助于增加你的自信心,有信心你的系统代码中不包含已知的、可预防的安全漏洞。