为科技创业公司做了5年代码审核后,我学到了什么?
作者|Ken Kantzer
链接|https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/
在 PKC 的时候,我们团队做了 20 多次代码审核。其中许多审核对象是处在 A 轮或者 B 轮的创业公司。(通常是在他们手头有钱,而且完成了决定生死的产品市场契合度评估后,觉得是该深度考虑一下安全问题的时候。)
这项工作其妙无穷。我们深入到许多栈和架构的交叉部分中,在不同的领域间游走。我们找到了各种各样的安全问题,大到灾难性的,小到只是有点意思的。此外,我们还有机会和资深工程师和 CTO 交流,更宽泛地探讨他们在开始推进时在工程以及产品上所面临的挑战。
考虑到有些审计已经是七八年前的事了,观察这些创业公司中有哪些还活的很好,而有哪些已经消失也十分有趣。
在此我想要分享一些令人惊奇的发现。我从众多观察中得出了这些结论,并大致按照从宽泛领域到安全上的细节问题罗列如下:
构建大型产品未必需要上百个工程师。
关于这个问题,我写了一篇更长的文章(https://kenkantzer.com/ou-dont-need-hundreds-of-engineers/)。但基本上,尽管我们审计的创业公司都处在十分相近的阶段上,它们工程师团队的规模却各不相同。让人惊讶的是,有时那些功能跨度最广,让人印象最深刻的产品出自较小的团队之手。而多年之后,正是这些“小而美”的团队能够在市场上横扫千军。
精简胜于精明。
身为一个自诩的精英主义者,我不愿承认这点,但它确实是对的:在我们审计过的创业公司中,活的最好的那些往往在工程上秉持「保持精简」的原则。为精明而精明让人反感。反过来说,那些让我们一看就觉得「我天,这些人好精明」的公司大多渐渐消亡了。通常情况下,连带出一堆问题的「七伤拳」(在先前的一篇文章 https://kenkantzer.com/5-software-engineering-foot-guns/ 中我对此做了更多讨论)是不成熟的微服务迁移,依赖分布式计算的架构,以及信息冗余的设计。
最重大的发现往往产生于审计之初和收尾阶段。
想想看是有道理的。审计刚开始的几个小时里,你找到的是最显眼的问题。接着,伴随着读取代码和测试基本功能,事情陷于停滞。到了最后几小时,你已经完全熟悉了这个新代码库的内容,问题逐渐浮出水面。
过去十年间,编写安全性高的软件变得更加容易了。
我没有在统计学意义上具有显著结果的证据来支持这个结论。但看起来,2012 年前每行代码的漏洞数要显著高于 2012 年后。(我们从 2014 年起开始做审计。)这可能是 Web 2.0 架构的原因,也可能是因为开发人员的安全意识有所提高。无论原因是什么,这都意味着如今软件工程师可用的工具和默认设置在安全性上总体上了一个台阶。
所有的重大安全漏洞都很明显。
我们所做过的审计中,可能每五个里就有一个能发现重大安全漏洞——严重到我们要立即给客户打电话提醒他们修复的程度。我不记得有哪个案例中的安全漏洞是十分复杂的。事实上,这也是构成漏洞严重性的一个方面。我们担心的主要是这些漏洞非常容易被发现并为人所用。“易发现性”已经是分析影响的一个指标,所以这不是什么新鲜看法。但我强烈认为易发现性应当被给予更高的权重。在实际的暴露中易发现性就是全部。黑客们很懒,他们要的就是最好找的漏洞。要是他们能利用暴露在返回结果中的令牌重置用户密码的话,他们甚至不会去考虑渗透一个严重的堆喷漏洞。(2016年的时候 Uber 就发现了这个问题。)对此的相反意见是看重易发现性会让「不被发现漏洞的安全」大行其道,因为它严重依赖于攻击者可能或者应该知道哪些东西的猜测。但要再次强调的是,依照个人经验,在实际操作中,易发现性是受到攻击的重要先兆。
框架和基础设施中默认安全的功能极大改善了安全状况。
对此,我也另写了一篇更长的文章。但总体上,React 这样的框架为了预防跨站脚本攻击 (XSS) 而避免掉了所有 HTML 。无服务架构则跳过开发者自动完成操作系统和网站服务器的配置。这些都极大改善了使用它们的公司的安全状况。而我们做过的 PHP 审计中则充斥着 XSS 。二者相较,可以看出尽管这些较新的框架不是完全不可渗透的,但它们在特定的几个领域缩小了可攻击面。这在实践中带来了很大不同。
单一仓库更易审计。
从对安全研究者的友好程度来说,单一仓库的审计难度要低于那些被划分为多个仓库的一系列服务。我们无需编写 wrapper script 以整合所用的多个工具,要弄清某块特定的代码是否被用在了其他地方也更容易。最重要的是,我们也无需担心不同仓库所用的函数库版本不统一。
整个审计的过程中,你可能都在追踪那些依赖中带有漏洞的函数库。
要辨别出依赖中的某个漏洞是否是可被利用的简直难如登天。我们整个行业在确保基础函数库的安全性问题上投资不足,这也是为何 Log4j 这样的事件能造成如此之大的影响。在这方面,Node 和 npm 绝对很危险,因为依赖链的规模大到难以估量。GitHub 发布 dependabot 无疑是一大幸事,因为大多数时候我们可以告诉客户按优先级完成升级就可以了。
千万不要把不可信数据反序列化。
这种情况大多发生于 PHP 中。出于一些原因,PHP 开发者热衷于将对象序列化或者反序列化,而不是使用 JSON 。但是,如我们所见,每当服务器对客户端对象进行反序列化和解析,可怕的攻击随之而来。以防有些人不知道,Portswigger 对可能产生问题的地方进行了详细的拆解。(恰巧集中在 PHP 上。是巧合吗?)简而言之,反序列化漏洞的共同点在于给予了用户操纵服务器所使用的对象的能力。这项能力极其强大,而且影响面广。它和原型污染以及用户生成的 HTML 模板在概念上相近。那该如何修复这一漏洞呢?最好是让用户发送 JSON 对象(它只允许寥寥几个数据类型),或者根据对象属性手动创建对象。工作量是大了一点,但是值得。
业务逻辑缺陷很罕见,但如果有,那问题就大了。
想想看 —— 业务逻辑上的缺陷势必会影响到业务。一个有趣的结论是,即便你使用的协议在构建之初就确保了安全性,像是业务逻辑缺陷这样的人为错误也屡见不鲜。(你只需要看看那些利用写的很烂的智能合约达成的攻击就明白了。)
自定义模糊测试惊人的有效。
代码审计做了两年后,我开始要求在所有的审计中使用自定义模糊器来测试 API ,授权,等等。某种程度上这是个通用的做法,我也是从 Thomas Ptacek 那里学到的这种思想。他在 Hiring Post 中提到了这一点。在我们做这件事之前,我其实觉得这是在浪费时间。我总觉得这是对工程学的误用,而且审计的时间最好是花在阅读代码和验证假说上。但事实是,模糊测试的有效性和效率都高的惊人,尤其是面向较大规模的代码库的时候。
公司兼并让安全问题复杂化了许多。
这意味着需要审核更多的代码模式,查看更多 AWS 招呼,以及和各色 SDLC 工具打交道。当然,兼并往往意味着要将全新的语言和框架并入到现有的模式中去。
一群软件工程师里至少有一个隐藏的安全领域爱好者。
这个人选常常令人惊奇,而且他们往往也反应不过来自己就是那个人。安全领域的技能越来越向着软件方向发展。这是个套利的好机会,如果这群人能被准确定位出来的话。
快速修复漏洞的能力往往与高水平的工程能力密切相关。
最贴切的例子是,我们有些客户让我们把发现的问题持续同步给他们,然后由他们自己随即修复漏洞。
基本上没人一次就能成功配置 JWT token 和 webhook 。
配置 webhook 时,人们总是忘了给传入的请求授权。(或者他们使用的服务本身不允许授权。真是一塌糊涂。)这类问题最终让我们的研究员之一 Josh 抛出了一系列疑问,并最终开启了一场 DefCON/Blackhat talk。JWT 则是出了名的难搞,哪怕你用了函数库,也会有无数操作让你无法在登出时让令牌正确失效,授权时错误检测了 JWT,或者默认保持信任。
MD5 依然被应用在很多地方,但这基本上是虚假繁荣。
MD5 在很多领域都有应用,除了作为一个(不)足够抗碰撞的密码哈希。比如说,因为速度快,它常被用来在自动测试中生成大量伪随机 GUID 。在这些情形里,MD5 不安全的特性不再重要,只不过你的静态分析工具会疯狂警告你。
我想知道你们是否见过这些,或者还有其它的问题。有任何异议都可以给我留言。