
React Server Components 版本 19.0.0、19.1.0、19.1.1 和 19.2.0 中的预授权远程代码执行漏洞。
本仓库包含对 CVE-2025-55182 的概念验证(PoC)复现,这是一个 React Server Components (RSC) 中的严重安全漏洞,允许未经身份验证的任意代码执行。
该漏洞存在于 React Server Components 从客户端请求反序列化 "Server Actions" 的方式中。具体来说,requireModule 函数未能验证请求的导出名称是否是模块的直接属性。这允许攻击者访问导出函数的 constructor 属性,从而获取全局 Function 构造函数的引用,进而可用于执行任意代码。
本 PoC 使用一个最小的 Node.js 环境来隔离 react-server-dom-webpack 库中的漏洞,确保该利用演示的是库本身的问题,而非框架配置错误。
npmnpm install
[!NOTE]
package.json已固定为漏洞版本19.0.0。
该脚本设置了一个原始 HTTP 服务器,使用存在漏洞的 React 运行时解码请求。
# tty1
node --conditions react-server server.js
在另一个终端中运行利用程序。该程序向服务器发送一个恶意的 Flight 负载。
# tty2
node exploit.js id
你会看到命令输出在响应中返回:
预期输出:
Response: uid=0(root) gid=0(root) groups=0(root)
漏洞为何会发生?
ReactFlightDOMServerNode.js 中的 requireModule 函数基本直接信任了客户端发送的任何 name。它执行了 moduleExports[metadata[NAME]],而没有检查该属性是否确实应该被暴露。因此,如果客户端说“老哥,我要这个属性”,服务器就直接回答“没问题!给你,老铁”。
为什么允许访问任意属性是个坏主意?
因为这基本上让任何人都能触及原型链,甚至包括 constructor,这非常危险。如果模块恰好导出的是一个函数(比如 module.exports = () => {}),那么它的 constructor 就是全局的 Function 构造函数。
获取 Function 构造函数为何意味着 RCE?
一旦攻击者获取了 Function 构造函数,他们就可以滥用“绑定的服务器动作”(Bound Server Action)功能。他们将包含恶意 JavaScript 的字符串绑定到该构造函数上(本质上将其转换为 new Function("evil code"))。一旦执行,服务器就会运行他们放入的任意代码。
React 为何会实际执行那个恶意函数?
因为服务器动作可以通过 ID 触发。如果攻击者构造的负载中包含一个指向其 module#constructor 引用的动作 ID,React 会像处理普通动作一样解析它并执行。那个“动作”实际上就是他们的恶意函数。
为什么这些都没有被验证?
系统默认假设服务器引用元数据中的 id 和 name 始终指向开发者定义的有效导出。没有使用类似 hasOwnProperty 的安全检查来确保请求的属性是真正的导出,而不是从原型链继承而来的。
server.js 而不是 Next.js?我使用原始的 server.js(以及辅助文件 webpack-runtime.js)手动配置 React Server Components 运行时。这使我们能够:
module.exports = fn),利用才能工作。真正的打包工具可能会根据其配置改变导出包装方式。react-server-dom-webpack 内部,而不是 Next.js。react-server-dom-webpack 假设它在 Webpack 包内运行。我们的 webpack-runtime.js 提供了它期望的全局变量(__webpack_require__, __webpack_chunk_load__)。
这不是在模拟漏洞,而是为库提供其运行所需的最小运行时。关于"无效的 PoC"已有讨论,这些 PoC 仅在开发者故意暴露危险内容(如 child_process.exec)时才有效。
本 PoC 不属于此类。它在正常、安全的配置下也能工作。
暴露的函数是无害的
应用程序暴露了一个简单的 updateProfile 函数,它只返回一个字符串,没有任何可疑内容,没有 shell 命令。
利用完全跳过了该函数
漏洞允许攻击者忽略安全的导出,直接跳转到 updateProfile.constructor,即全局 Function 构造函数。
核心问题是属性访问
React 不应该允许访问 .constructor。开发者并没有打算暴露 Function 构造函数,而是不安全的反序列化替他们做了。
唯一真正的要求是模块直接导出一个函数(module.exports = fn),这在 CommonJS 和许多打包工具配置中非常常见。
exploit.js 中的负载构建了一个包含三个块的 React Flight 消息:
id:"user-profile-action#constructor",意思是“给我构造函数”。bound:指向包含参数的块 2。["console.log('nice try, diddy!')"]:恶意代码字符串。当 React 反序列化时:
user-profile-action。.constructor 属性 => 获得全局 Function。new Function("console.log('nice try, diddy!')")这就是 RCE!
立即升级到修复版本:
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1该补丁引入了 hasOwnProperty 检查,以防止访问继承属性,并限制了 base64 文件上传。
如果针对修复版本运行此 PoC,服务器将崩溃或报错:
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
这确认了利用未能访问 constructor 属性(返回了 undefined 而非 Function),因此后续的 .bind 调用失败了。
此代码仅用于教育和测试目的。请勿将本利用用于您不拥有或未获得明确测试许可的系统。
以 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 发布。