为初创团队审计代码五年——我从中学到的

本文翻译自《Learnings from 5 years of tech startup code audits》一文,译文标题略微进行了改动;原作者 Ken Kantzer 在软件工程领域经营多年,对安全性问题有着深入思考;感谢 Twitter 用户 @greatested 分享了文章英文版本

我在 PKC 的那段时间,我们的团队做了超过二十次的代码审计,其中大多数,是为那些已经完成 A 或者 B 轮融资的初创企业进行的(经常是当这些企业已经渡过了市场契合度的挑战,手里有现金并且意识到最好应该深入研究安全性的时候)。

这是一项引人入胜的工作——我们跨越广泛的领域,并且深入到了各种堆栈和架构的组合。我们发现了从“有点意思的”一直到“灾难性的”各种安全问题。并且我们也有机会与高级研发领导以及 CTO 进行广泛交谈,了解他们在开始拓展时遇到的各种工程性和产品上的挑战。

同样地,看看这些初创企业中哪些做得很好、哪些已经褪色也很吸引人,因为现在距离当时审计已经过了七、八年了。

我想在此分享一些我从这些观察中内化出来的、令人惊讶的事情,大致从最普遍到最具体的安全问题进行排序。

  1. 你并不需要几百名工程师才能构造出好的产品。对此我有一段比较长的解释,重点是,尽管我们审计的初创企业的一般阶段都很相似,不过研发团队的规模可谓千差万别。令人惊讶的是,有时候,最令人印象深刻的、功能范围最广的产品通常是由较小的团队构建的。而正是这些“小而美”的团队,在几年之后将会重塑他们所在的市场。
  2. 简单胜过聪明。作为一名自认的精英主义者,我很不愿意这么说,但这是事实:我们审计过的那些做得最好的初创企业,几乎都在研发中宣扬“保持简单”的方法论。”为了聪明而聪明“的做法是令人厌恶的。另一方面,那些喜欢“哇,这些人真是聪明绝顶”的公司大多已经消失了。一般来说,让很多(团队)陷入困境的主要原因(我之前写过一篇文章做过深入讨论)是过早地使用微服务、依赖分布式的计算架构,以及严重依赖消息传递的设计。
  3. 影响最大的发现总是出现在审计刚开始和最后的几个小时。如果你仔细思考的话,这是有道理的:在刚开始审计的几个小时里,你就能低成本地发现一些“果实”。仅仅通过搜索代码和基本功能测试,你就能发现一些像拇指一样突出的东西。在最后的几个小时里,你已经完全适应了新代码库,因此事情开始变得简单。
  4. 最近 10 年来,编写安全的软件已经变得非常容易。我没有统计学上的证据来支持这一点,但似乎在 2012 年前后编写的代码比 2012 年后编写的代码在每个 SLOC(译者注:原始代码行数) 上的漏洞要多得多。也许是因为 Web 2.0 框架的缘故,又或是开发人员安全意识的提高。不管是什么原因,我觉得就目前软件工程师拥有的工具以及默认值而言,安全问题从根本上得到了改善。
  5. 那些真正严重的安全漏洞都很明显。在我们所做过的代码审计中,大约五分之一会发现有“大漏洞”——糟糕到我们必须立刻打电话给客户让他们修复的那种漏洞。我记不太清有哪个案例中的漏洞是非常“聪明型的”。事实上,这也是使这些最糟糕的漏洞变得糟糕的部分原因——因为它们很容易被发现并被利用。“可发现性”作为影响分析的因素已经有一段时间了,所以这并不新鲜。但是我想的是,可发现性(discoverability)应该被放到更重要的位置上。当涉及实际利用时,可发现性就是一切。黑客都是很懒的,他们总是会去拿成本最低的那颗果实。如果他们可以根据响应中的重置令牌来重设用户密码(Uber 在 2016 年发现的那样),那么他们甚至就不会关心即使非常严重的堆喷射漏洞。对此的反驳是,高度重视“可发现性”会使“隐蔽性安全”永久化,因为它非常依赖于猜测攻击者能够,或者应该知道些什么。但同样,个人经验再次强烈表明在实践中,可发现性仍然是实际利用的一个很好的预测指标。
  6. 来自框架与基础架构中的预设安全性功能大幅提升了安全性。我为此也写过一段长文解释,但是关键的是,像 React 这样的框架默认转义所有的 HTML 来避免跨域脚本,一些无服务器(serverless)的技术堆栈将操作系统与 Web 服务器的配置权从开发者手中移走,极大的提升了使用这些技术的企业的安全性。将此与我们的 PHP 审计相比较,后者充满了 XSS 攻击。这些新的堆栈/框架并非是不可攻克的,但是它们的可攻击面较小,而这正是在实践中产生巨大差异的地方。
  7. 单体仓库更易于审计。从安全研究人员的功效学角度来看,审计单个仓库比起一系列分布在不同代码库中的服务要容易。这样就不需要对于我们的各种工具编写包装脚本了。也更容易确定一段给定的代码是否在其他地方被使用。更重要的是,无需担心公共库的版本号在不同的仓库间不同。
  8. 很容易就把整个审计的时间都花在了寻找有漏洞的依赖库中。判断依赖库中的一个漏洞是否可以被利用非常非常困难。作为一个行业,我们在确保基础库安全的方面的投资肯定不足,这就是为什么像 Log4j 这件事影响这么大。Node 和 npm 在这方面绝对是可怕的——依赖关系链是根本不可审计的。当 GitHub 发布 dependabot 时绝对是巨大的获益,因为我们可以告诉我们的客户按照优先顺序进行升级。
  9. 永远不要反序列化不信任的数据。这一点经常发生在 PHP 上,因为处于某些原因,PHP 开发者喜欢序列化/反序列化对象而非使用 JSON。但是几乎在每个案例中,我们都发现当服务端反序列化由客户端传入的对象时,都有巨大的安全漏洞。对于不熟悉这个问题的人,Portswigger 对可能出现的问题进行了详细拆解(顺便说一下,重点是 PHP,是巧合?)。简言之,所有反序列化漏洞的共同点是:允许用户通过操控随后由服务器使用的对象,这是一种非常强大的能力,并且具有广泛的影响。它在概念上类似于 prototype 污染、以及用户生成的 HTML 模版。如何修复呢?最好是让用户发送一个 JSON 对象(它只有较少种类的数据类型)并且手动地依据每一个数据项去构建对象。这样工作量会稍微大一些,但是非常值得!
  10. 业务逻辑缺陷很少见,但是往往是非常糟糕的。想一想——业务逻辑中的缺陷肯定会影响业务。一个有趣的推论是,即使你的协议是依可证明的安全性来构造的,但是来自业务逻辑中的糟糕人为错误也出奇地普遍(你只需要看看那些写得不好的智能合约,那一系列绝对属于毁灭性的漏洞)。
  11. 自定义的模糊测试出乎意料地有效。在我们做代码审计的几年里,我开始要求我们所有的对代码的审计都要构建一个自定义的模糊器,用以测试产品的 APIs,以及身份认证之类的。这是一个很常见的错误,我们是从 Thomas Ptacek 那里学到的这个想法,他当时在他的招聘贴里提及了这一点。但当我们这样做之前,我认为这实际上是在浪费时间——我认为这只是一个工程上的误用的例子,而审计的时间最好多用在阅读代码,以及尝试各种假设上。不过就花费的时间而言,模糊测试的效果和效率非常令人惊讶,尤其是在较大的代码库上。
  12. 收购使安全性问题变得相当复杂。有更多的代码模式需要审查、更多的 AWS 账号需要被关注,SDLC 工具(译注:软件开发生命周期工具)的种类也更多。当然,通常收购意味着一整套全新的语言和/或框架,它们有它们自己的使用模式。
  13. 在软件工程师中,至少有一位秘密的安全爱好者。“他是谁”经常会让人很惊讶,并且几乎没人知道!随着安全技能越来越偏向于软件领域,如果这些人能够被准确识别出来,就会有巨大的套利空间。
  14. 快速周转修复漏洞通常意味着卓越的通用工程操作。最好的场景是客户要求我们不断地向他们提供我们发现的任何东西,他们会立即修复它。
  15. 基本上没人能够在第一次使用 JWT 的时候就把令牌和 webhook 做对。以 webhooks 来说,人们几乎总是忘记对传入的请求进行认证(或者他们使用的服务不允许做认证……这是很混乱的!)。这类问题导致我们的研究人员 Josh 开始提出一系列问题,并有了在 DefCON/Blackhat 的演讲。众所周知,JWT 很难被正确处理,即使通过调用库也是如此,并且有许多实现未能在注销时正确地使令牌过期、错误地检查 JWT 的真实性,或者只是默认地信任它。
  16. MD5 仍然在很多地方使用,但大多是误报。事实证明,除了用于(不)充分抗冲突的密码哈希之外,MD5 还用来做许多其他的事情。比如,因为它非常快,所以它经常会被用于在自动化测试中快速生成大量的伪随机 GUIDs。在这些情况下,MD5 的不安全性就变得无关紧要了,尽管你使用的静态分析工具可能会对你大喊大叫。

很好奇你是否已经听说过其中的一些点了!并且如果你不同意的话,请尽管告诉我!