前几周都看到过这个消息,只知道出现鉴权漏洞,没有详细关注内容,因为公司有做网安方面的团队,恰好今天看到一个视频对此漏洞的分析一针见血,比较感兴趣,遂水一下文,文中部分观点来自视频。总结就是世界是一个巨大的草台班子 🤣。

Next.js 中间件鉴权漏洞(吃瓜)

漏洞影响

3.21 日,CVA 收到了一个平平无奇的安全漏洞,而主角就是 Nextjs,对于前端程序员来说,Nextjs 就算没用过也听说过,况且我们近期也确实在使用 Nextjs 开发业务,此漏洞的影响范围非常广,具体范围如下。

image

状况惨烈,可以说从 v11 开始直到漏洞发现的 v15 版本,此漏洞一直存在,之所以此漏洞能够引起全然大波,因为其危险程度极高。

CVSS 评分 9.1,六大核心指标只有 Availability(可用性)不受影响,其余都是最糟糕的,甚至来说,Availability 也是很糟糕的,因为攻击者通过此漏洞使用 DDOS 攻击,可以轻松让系统宕机

漏洞技术细节

这个漏洞是非常低级的,低级到连我这种菜鸡都忍不住吐槽:好低级的 bug

通常,一个后端系统在接收到一个前端请求时,要经历很多的前置步骤才能够得到结果,比如鉴权、权限控制、节流 等等,而这些就拿 nest 来说,这些都属于中间件层面的东西,而 Nextjs 中自然也存在 middleware

而与其它开源项目不同,Vercel 作为一个盈利公司,以 Nextjs 这个非盈利项目来给自家的 服务器+cdn 引流,在这种情况下,自然免不了在 Nextjs 中添加大量对自家产品的支持优化,比如在架构设计上,Nextjs 就有一个与众不同的地方。

Next 中间件的工作流程

当你有一个 Nextjs 项目部署在 Vercel 云上时,代码中涉及到中间件的部分就会被自动提取出来,发送到 Vercel Edge Network,也就是 Vercel 自家 CDN(突然有点明白为什么 middleware 只能在 edge runtime 模式中使用,直到 15.2 才实验性支持 node)

image

当用户发起请求时,CDN 会先执行全部中间件的代码逻辑,然后将剩余部分交给服务器

这个其实就很奇怪了,CDN 作为一个静态服务,为什么要执行代码逻辑?而 Vercel 的云服务之所以快,有一部分原因也是因为:在 CDN 层做处理就能返回结果的东西,就不会再去请求服务器了,这是想当然的快。

比如我要请求一个用户信息,请求会先经过 CDN,CDN 进行身份认证,一旦发现未认证,就会将其重定向到 /login 页面,整个过程都不需要服务器参与

image

但是这样做有一个隐患,就是 CDN 部署的场景,CDN 重定向到 /login 页面,而 /login 页面就是在 CDN 上的,就相当于 CDN 当场将请求重定向到 CDN,这种情况下就会陷入一个死循环。

为了解决这个问题,Vercel 团队给出了解决方案,就是在重定向之前,给新请求添加一个特殊的 header x-middleware-subrequest,就是为了告诉未来的自己,这个请求不是来自于用户,而是 CDN 在自产自销,于是乎,当这个解决问题的 pr 被合并后,漏洞的起因就出来了

image

而负责身份认证和重定向的中间件,发现存在这个 header,就知道不需要干活了,直接放行

这里代码不完整,实际上并不是仅仅判断 x-middleware-subrequest ,而是每次递归请求都会给这个字段的值加上一个 middleware,如果相同的 middleware 超过5次,就会响应一个 x-middleware-next: 1,表示跳过中间件

也就是说当请求重定向超过 5 次时,next 就认为这个是自家 CDN 造成的死循环,就会不走中间件,直接放行。

image

好了,到这时候,就能很明显看出问题了,有没有安全漏洞呢?那可太有了。

因为 header 是非常轻松就可以伪造的,也就是说,只要有 x-middleware-subrequest 这个 header,后端就会傻乎乎的跳过中间件逻辑,直接放行,随意访问服务资源,什么 DDOS、缓存污染、Auth认证等,那都不是事儿

可以说只要是一个正常的开发人员,都不会想出这种离谱操作,而偏偏 Vercel 年薪百万的程序员想到了,这何尝不是另一种聪明绝顶呢?而且整整三年半的时间,都没有人觉得有问题,what the fuck?

起止时间线

完整时间线可查看 Vercel 资源博客

第一次这个 pr 漏洞被 merge,是在 2021年10月,直到 2025年03月1日,被外部专家发现并告知 Vercel,Vercel 才开始重视起来。

image

巨大的草台班子

Vercel 在 3月1日 接到漏洞到真正发布补丁,历时 17 天

image

而在这 17 天,Vercel 团队探讨这么久,想出的补救方式是什么呢?就是再加一个 header x-middleware-subrequest-id,我不禁 ???

image

在重定向之前,Nextjs 会把生成的随机数保存在全局对象中,等到新请求回来时,拿全局对象的随机数和 header 中的值做对比,这样就确保外部用户无法伪造这个 header。(他简直是个超人.jpg)

image

其实这个补丁从结果上说确实可以,但是抽象程度在于,他解决这个问题的同时,反手制造了其它问题。

  • 首先,加密随机数的成本并不低,尤其在 CDN 这种冷启动场景下,这样操作,相对于把 Vercel 花那么久挤出来的速度优势全搞没了
  • 其次,这种做法不一定有用,因为重定向后的请求并不一定回到同一个 session,甚至按照 CDN 的分布式设计,都不一定回到同一台机器上

image

如果说最开始的漏洞是失误造成的,那此次补丁就不得不让人怀疑 Vercel 程序员的脑洞了😅

而如此牛逼的 Vercel,又顶着压力拖了一周才发布了新补丁

image

而这次就更令人惊呆了,直接掀桌子,把所有 header 绕过中间件的逻辑全部删除,意思就是死循环问题 Vercel 不管了,用户自己解决。

吐槽

这次瓜吃的还可以,Vercel 团队其实做的也没错,删除这些边界 case,让用户自行解决,本来就是最好的解决方法。其实很多开源项目都存在这种现象,为了用户的 DX,强行在边缘情况上做些妥协,就导致很多不必要的判断,而这些判断可能会影响其它地方。

不过能新增这种 header 绕过的漏洞,属于是外行看了都想笑的,bug 归 bug,bug 太低级也是很让人无语的。