我在 PKC 公司工作时,做过 20 多次代码审查,其中许多客户是刚刚获得 A 轮或 B 轮融资的初创公司。他们这个时候通常有余钱,已经关注过产品 - 市场契合度里的那些“不做就会死”的点,于是觉得最好深入研究一下安全问题,所以来找我们审查代码。
这项工作令人废寝忘食,我们深入研究了技术栈和架构的切面,横跨各种领域,发现了各式各样的安全问题,有些是灾难级的,也有些只是单纯好玩,不一而足。我们还有机会跟他们的技术高管和 CTO 谈笑风生,聊聊他们刚刚开始拓展时所面临的工程和产品方面的挑战。
其中一些审查已经过了七八年,如今再去观察一下这些初创公司,看看哪些公司小日子过得还不错,哪些已经销声匿迹,这也挺有意思。
不过,我这里想分享的是我从这些观察中领悟到的一些事情,可能会有点出人意料。我大致按与安全的相关性从低到高排列了顺序。
1.构建出色的产品不需要几百个工程师
我还为此写了一篇长文。尽管找我们审查代码的这些初创公司,起步阶段都差不多,但技术团队规模相差很大。出人意料的是,有时最令人印象深刻的、功能应用最广泛的产品是由较小团队构建的。几年后,正是这些“小强”团队,在他们自己的市场里风生水起。
2.简单比聪明好
我自认是精英主义者,好吧,我不得不痛苦地接受这一事实:我们审查过的初创公司里面,现在做得最好的,它们通常都有一种近乎公理的“保持简单”的工程方法。反过来说,自作聪明令人厌恶,那些让我们觉得“哇,这些家伙鬼精鬼精的”的公司大多已经消失了。一般来说,让很多公司陷入困境的主要“脚炮”,在于过早地转向微服务、依赖分布式计算的架构和重消息传递的设计。
3.审查中影响最大的结果总是出现在前几个小时和最后几个小时内
仔细想想,这很有道理:最初的几个小时里,明显的问题很容易就会冒出来——只需要 grep 代码或测试一些基本功能,它们就会露出马脚;而在最后的几个小时里,你已经完全融入了他们的代码库,因此也更容易发现问题。
4.近 10 年来,编写安全软件已经变得非常容易
我没有做过统计,但感觉 2012 年之前,每个 SLOC(译注:源代码行数)的漏洞似乎比 2012 年之后的代码要多得多(我们从 2014 年开始审查)。也许是因为用了 Web 2.0 框架,或者是开发人员提高了安全意识。不管是什么,我认为就软件工程师现在可用的工具和默认设置而言,安全性确实在根本上得到了改善。
5.所有严重的安全漏洞都很容易检查出来
在我们审查过的代码中,可能有五分之一都会发现“大鱼”——非常严重的漏洞,以至于我们得马上打电话告诉客户,让他们赶紧修复。我不记得有哪个案例的漏洞聪明到需要七绕八拐才能发现。这正是那些严重漏洞之所以严重的一个原因——它们很容易被发现和利用。
“可发现性”(被攻击者发现)一直是代码漏洞分析的组成部分,所以在代码漏洞分析时发现漏洞很常见。但我还是认为应该更重视漏洞的可发现性。当漏洞没有暴露出来时,被发现是个概率事件;但被爆出来时,它就是 100%。黑客很懒的,他们喜欢摘树上最低的果子。
如果用户的重置令牌就放在响应中(正如优步在 2016 年前后发现的那样),黑客可以直接拿来重置用户的密码,他们才懒得去管什么堆喷射漏洞,即使它们更严重。可能有人会反对,说太过重视可发现性,会使“隐蔽性安全”永久化,因为它非常依赖于猜测攻击者能知道或应该知道什么。但同样地,根据我个人经验,在实践中,可发现性可以用来给安全性作为一个很好的预测指标。
6.框架和基础设施中提供的预设安全设置,极大地提高了安全性
我为此也写过一篇长文。但本质上,诸如 React 默认转义所有 HTML 代码以避免跨站点脚本,以及无服务器技术栈把操作系统和 Web 服务器的配置从开发人员手里抢过来(避免了他们的失误),这些也都显著提高了安全性。与之相比,我们审查过的 PHP 代码则充斥着 XSS。这些较新的技术栈 / 框架并非毫无破绽,但它们的可攻击面较小,这恰恰是在实践中能产生巨大差异的地方。
7.单一仓库更容易审查
从人体工程学的角度来看,安全研究人员审查一个单一仓库,比审查一系列服务拆分成的不同代码库更容易。单一仓库让我们无需围绕各种工具编写包装脚本、更容易确定一段代码是否被其他地方调用,最重要的是,不用担心公共库在不同仓库里有版本冲突。
8.寻找依赖库中的漏洞很容易就会把所有时间都搭进去
要判断一个依赖库中的特定漏洞是否会被利用,这很困难。我们行业在保护基础库方面的投入不足,这就是为什么像 Log4j 这样的库暴雷影响这么大。在这方面,Node 和 npm 绝对是可怕的——我们不能去审查里面的依赖链。GitHub 发布的 dependabot 是个大红包,在大多数情况下,我们就可以直接告诉客户按优先顺序升级东西了。
9.永远不要反序列化不受信任的数据
这种情况在 PHP 中发生得最多,因为出于某种原因,PHP 开发人员喜欢序列化 / 反序列化对象而不是使用 JSON。但是,我们发现几乎所有情况下,服务器在反序列化和解析客户端传来的对象时都会导致可怕的漏洞。不了解的人可以看这里:Portswigger 对可能出现的问题做了细分(顺便说一下,集中在 PHP。你说这是巧合吗?)。
总之,所有反序列化漏洞的共同点是,用户能够操纵服务器之后会使用的对象,这是一种非常强大的能力,攻击面很大。在概念上,它类似于原型污染,也类似于让用户自行生成 HTML 模板。想要修复方案?让用户发送 JSON 对象呗(它可用的数据类型非常少,因此难以用来攻击),然后根据对象中的字段,手动构造,这要好得多。工作量稍微大一些,但非常值得!
10.业务逻辑缺陷很少见,但一旦出现,往往非常糟糕
设想一下——业务逻辑中的缺陷肯定会影响业务。一个有趣的推论是,即使你的协议就是为了提供可证明安全的东西而构建的,但人为失误也会极其普遍,它们会以糟糕的业务逻辑形式出现(看看那些利用写得不好的智能合约搞出来的一系列绝对毁灭性的漏洞)。
11.定制模糊测试非常有效
在我们审查代码几年后,我开始要求我们所有的代码审查要包含定制模糊器来测试产品 API、身份验证等。在某种程度上这很常见,比如我就是从 Thomas Ptacek 那里偷来的这个想法,他在职位招聘信息里提过。以前我一直认为这是浪费时间,直到做过了才发现“真香”——我一直认为这只是一个误用工程的例子,审查时间最好花在阅读代码和尝试各种假设上。但事实证明,模糊测试在花费时间方面非常有效和高效,尤其是在更大的代码库上。
12.收购使安全性更复杂
要看的代码模式更多了,AWS 账户更多了,SDLC 工具种类更多了。当然,收购通常意味着换成其他语言和 / 或框架,而它们都有各自的使用模式。
13.软件工程师团队里总是隐藏着至少一位热衷于安全的人
几乎没人能一眼看出他们是谁。随着安全技能越来越向软件倾斜,准确地挖掘出这样的人才就像捡到宝。
14.快速修复漏洞的能力,跟对一般工程的优秀运维能力有关
最好的情况是,客户要求我们一旦发现问题就马上向他们反馈,他们会立即修复问题。
15.几乎没有人在第一次用 JWT 令牌和 webhook 时就用对了
用 webhook 时,人们几乎总是忘记对传入的请求进行身份认证(也可能是他们使用的服务不允许认证……这真挫!)。我们一位研究人员 Josh,对这类情况提出了一系列问题,促成了 DefCON/Blackhat 演讲。众所周知,即便使用一些现成的库,也很难正确处理 JWT。很多实现代码,不能在注销时正确地让令牌过期,或者错误地认证 JWT,或者直接默认相信它。
16.多地方仍然在用 MD5,但大多数情况下这没问题
实际上,MD5 除了计算密码散列值时用来抗碰撞(并不能)之外,还可以用来做许多其他事情。比如,因为它算起来非常快,所以在自动化测试里,经常用来快速生成大量伪随机的 GUID。这时,MD5 安不安全其实不重要,但静态分析工具可能还是会扯着喉咙报警。(忽略它们吧)
不知你是否看过这些观点。如果你有好的想法,欢迎留言。
原文链接:
https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/
评论