前几周都看到过这个消息,只知道出现鉴权漏洞,没有详细关注内容,因为公司有做网安方面的团队,恰好今天看到一个视频对此漏洞的分析一针见血,比较感兴趣,遂水一下文,文中部分观点来自视频。总结就是世界是一个巨大的草台班子 🤣。
3.21 日,CVA 收到了一个平平无奇的安全漏洞,而主角就是 Nextjs,对于前端程序员来说,Nextjs 就算没用过也听说过,况且我们近期也确实在使用 Nextjs 开发业务,此漏洞的影响范围非常广,具体范围如下。
![]()
状况惨烈,可以说从 v11 开始直到漏洞发现的 v15 版本,此漏洞一直存在,之所以此漏洞能够引起全然大波,因为其危险程度极高。
CVSS 评分 9.1,六大核心指标只有 Availability(可用性)不受影响,其余都是最糟糕的,甚至来说,Availability 也是很糟糕的,因为攻击者通过此漏洞使用 DDOS 攻击,可以轻松让系统宕机
这个漏洞是非常低级的,低级到连我这种菜鸡都忍不住吐槽:好低级的 bug
通常,一个后端系统在接收到一个前端请求时,要经历很多的前置步骤才能够得到结果,比如鉴权、权限控制、节流 等等,而这些就拿 nest 来说,这些都属于中间件层面的东西,而 Nextjs 中自然也存在 middleware。
而与其它开源项目不同,Vercel 作为一个盈利公司,以 Nextjs 这个非盈利项目来给自家的 服务器+cdn 引流,在这种情况下,自然免不了在 Nextjs 中添加大量对自家产品的支持优化,比如在架构设计上,Nextjs 就有一个与众不同的地方。
当你有一个 Nextjs 项目部署在 Vercel 云上时,代码中涉及到中间件的部分就会被自动提取出来,发送到 Vercel Edge Network,也就是 Vercel 自家 CDN(突然有点明白为什么 middleware 只能在 edge runtime 模式中使用,直到 15.2 才实验性支持 node)
![]()
当用户发起请求时,CDN 会先执行全部中间件的代码逻辑,然后将剩余部分交给服务器
这个其实就很奇怪了,CDN 作为一个静态服务,为什么要执行代码逻辑?而 Vercel 的云服务之所以快,有一部分原因也是因为:在 CDN 层做处理就能返回结果的东西,就不会再去请求服务器了,这是想当然的快。
比如我要请求一个用户信息,请求会先经过 CDN,CDN 进行身份认证,一旦发现未认证,就会将其重定向到 /login 页面,整个过程都不需要服务器参与
![]()
但是这样做有一个隐患,就是 CDN 部署的场景,CDN 重定向到 /login 页面,而 /login 页面就是在 CDN 上的,就相当于 CDN 当场将请求重定向到 CDN,这种情况下就会陷入一个死循环。
为了解决这个问题,Vercel 团队给出了解决方案,就是在重定向之前,给新请求添加一个特殊的 header x-middleware-subrequest,就是为了告诉未来的自己,这个请求不是来自于用户,而是 CDN 在自产自销,于是乎,当这个解决问题的 pr 被合并后,漏洞的起因就出来了
![]()
而负责身份认证和重定向的中间件,发现存在这个 header,就知道不需要干活了,直接放行
这里代码不完整,实际上并不是仅仅判断
x-middleware-subrequest,而是每次递归请求都会给这个字段的值加上一个middleware,如果相同的middleware超过5次,就会响应一个x-middleware-next: 1,表示跳过中间件
也就是说当请求重定向超过 5 次时,next 就认为这个是自家 CDN 造成的死循环,就会不走中间件,直接放行。
![]()
好了,到这时候,就能很明显看出问题了,有没有安全漏洞呢?那可太有了。
因为 header 是非常轻松就可以伪造的,也就是说,只要有 x-middleware-subrequest 这个 header,后端就会傻乎乎的跳过中间件逻辑,直接放行,随意访问服务资源,什么 DDOS、缓存污染、Auth认证等,那都不是事儿
可以说只要是一个正常的开发人员,都不会想出这种离谱操作,而偏偏 Vercel 年薪百万的程序员想到了,这何尝不是另一种聪明绝顶呢?而且整整三年半的时间,都没有人觉得有问题,what the fuck?
完整时间线可查看 Vercel 资源博客
第一次这个 pr 漏洞被 merge,是在 2021年10月,直到 2025年03月1日,被外部专家发现并告知 Vercel,Vercel 才开始重视起来。
![]()
Vercel 在 3月1日 接到漏洞到真正发布补丁,历时 17 天
![]()
而在这 17 天,Vercel 团队探讨这么久,想出的补救方式是什么呢?就是再加一个 header x-middleware-subrequest-id,我不禁 ???
![]()
在重定向之前,Nextjs 会把生成的随机数保存在全局对象中,等到新请求回来时,拿全局对象的随机数和 header 中的值做对比,这样就确保外部用户无法伪造这个 header。(他简直是个超人.jpg)
![]()
其实这个补丁从结果上说确实可以,但是抽象程度在于,他解决这个问题的同时,反手制造了其它问题。
![]()
如果说最开始的漏洞是失误造成的,那此次补丁就不得不让人怀疑 Vercel 程序员的脑洞了😅
而如此牛逼的 Vercel,又顶着压力拖了一周才发布了新补丁
![]()
而这次就更令人惊呆了,直接掀桌子,把所有 header 绕过中间件的逻辑全部删除,意思就是死循环问题 Vercel 不管了,用户自己解决。
这次瓜吃的还可以,Vercel 团队其实做的也没错,删除这些边界 case,让用户自行解决,本来就是最好的解决方法。其实很多开源项目都存在这种现象,为了用户的 DX,强行在边缘情况上做些妥协,就导致很多不必要的判断,而这些判断可能会影响其它地方。
不过能新增这种 header 绕过的漏洞,属于是外行看了都想笑的,bug 归 bug,bug 太低级也是很让人无语的。