Next.js Conf 2023 上团队演示 RSC 时,推特就吵过一轮「前端也能写后端漏洞」——如今一语成谶。
之前写过 Next.js CVE-2025-29927(middleware 鉴权绕过),那是”门卫”问题;React2Shell 是另一个量级——React Server Components(RSC)链路的 RCE。原文为五篇连载,作者参与应急并拿下 Vercel 官方 WAF 绕过活动奖金。
0x01 前置:从回调地狱到 Thenable
- 回调地狱:古早 API 全靠回调,
登录拿token → 查用户id → 查订单层层嵌套; - Promise(ES6):嵌套变链式,
then()让回调回到同一上下文; - Thenable:任何实现了
then()方法的对象。React 处理数据时会检查”值是不是 Thenable,是就调用它的then”——漏洞引信就是它。
0x02 SSR vs RSC:前端怎么写出了后端漏洞
- SSR:首渲染在后端返回 HTML,但仍要下载全部组件代码、靠 Hydration 绑定交互 → JS 体积大、TTI 长;
- RSC:组件留在服务端,客户端只收序列化结果——协议即 React Flight,解析执行都在服务端。
攻击面:Flight 的解析端是服务端代码,处理的却是可被伪造的客户端数据。
0x03 应急视角:为什么 SCA 扫不到这个组件
- 按通告排查
react-server-dom-webpack等组件——npm 日志 + SCA(软件成分分析)都没有命中; - 公司明明有 Next.js 业务:RSC 对 Next.js 属可选依赖,开发没装;
- 拿 Vulhub 源码调试验证 → Next.js 把第三方依赖直接打包进
compiled目录,SCA 清单里看不到react-server-dom-webpack。
教训:框架自打包依赖会绕过成分分析,SCA 说”没有”不等于”不受影响”,要以调试为准。
0x04 Flight 协议:Chunk 是怎么被解析的
RSC 数据包由一个个 Chunk 组成,解析流程:
遍历 multipart 字段
→ 每个字段 JSON.parse() 成对象
→ 递归遍历所有子孙结构
→ 发现 $ 开头的值 → 按规则互相"引用"(扩展 JSON 不支持的类型:函数/组件/Thenable…)
→ 结果放进 chunk.value,作为"数据"
$ 前缀引用是 Flight 对 JSON 的类型扩展,也是攻击者往解析里塞逻辑的入口。
0x05 漏洞本质:还是”混淆数据和业务逻辑”
万能公式:SQL 注入是输入变成 SQL、命令注入是输入变成命令、XSS 是输入变成 HTML——React2Shell 是攻击者的”数据”变成了服务端的”Chunk 对象”。
POC:POST 一个 multipart 请求(Next-Action: x 头 + Content-Type: multipart),用 $ 引用构造出带 constructor 的原型链,让解析器把攻击者控制的”数据”识别成可调用对象(Thenable/函数路径),触发执行 → RCE。
JSON.parse 的产物本该只是数据,$ 引用 + 原型链让解析器亲手把它升格成了逻辑。
0x06 15 万美元的 WAF 绕过
思维实验级
- payload 是 JSON → 用 JSON 的 unicode 转义改写关键词:
constructor写成\u0063onstructor,能否过关取决于 WAF 是否解码后再匹配; - 超大数据包:部分 WAF 为效率会放过超大 body(研究价值低)。
工程级
- Next.js 接收请求的缓冲区默认 1MB——payload 体积策略要贴着这个”预算”设计;
- 核心是找 WAF 解析与后端解析的差分点(谁多解码一层、谁先到缓冲上限),与SQL 注入绕过里”找解析差异而非规则漏洞”是同一条方法论。
0x07 小结与防守清单
- 升级到官方修复版本;WAF 对
Next-Action头 + multipart +$引用结构的组合流量做检测; - 应急别只信 SCA:Next.js 类自打包框架用
compiled目录实测; - 这个洞横跨 JS 异步机制、框架协议设计、原型链、WAF 对抗——追一个新洞追到底,胜过刷一百个旧洞。