5年在科技初创公司进行安全代码审计的经验教训


当我在PKC工作时,我们的团队做了超过20次的代码审计,其中许多是为刚刚进入A轮或B轮的初创公司做的(那通常是当他们有了现金,并意识到在关注产品的市场适应性之后,对其安全性进行更深入的研究是很好的)。

这是一项令人着迷的工作--我们深入研究了各种领域的堆栈和架构的巨大截面。我们发现了各种各样的安全问题,从灾难性的到普通有趣的。我们也有机会与高级工程领导层和首席技术官交谈,了解他们在刚开始扩大规模时所面临的工程和产品挑战。

看到这些初创公司中哪些做得很好,哪些已经消逝,也是很吸引人的,现在这些审计中的一些是7-8年前的。

我想和大家分享一下我从这些观察中内化出来的一些更令人惊讶的东西,大致上从最普遍的到最具体的安全问题排序:

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