通过数据二极管发送日志、警报和遥测数据

了解详情
我们利用人工智能进行网站翻译,虽然我们力求准确,但不一定总是 100%精确。感谢您的理解。

React2Shell (CVE-2025-55182):ReactServer 中的严重远程代码执行漏洞

作者: Loc Nguyen, Penetration Test Team Lead
分享此贴

CVE-2025-55182 是 ReactServer 中一个关键性的、无需身份验证的远程代码执行漏洞,其 CVSS 评分为 10.0——这是最高级别的严重性评级。 作为OPSWAT 研究员计划的一部分,我们的研究员对该漏洞进行了全面的技术分析,深入探究了其根源(即 React Flight 反序列化协议中的缺陷)、完整的利用链,以及该漏洞对现代网络生态系统造成的广泛影响。本文将介绍我们的研究发现,并为安全防御人员提供切实可行的应对建议。

React 已成为全球应用最广泛的前端库之一,为大量现代 Web 和mobile 提供支持。 Stack Overflow 的开发者调查始终将 React 列为顶级 Web 框架之一,其采用率在全球专业开发者中超过 40%。随着这一增长,React 团队推出了 ReactServer (RSC)作为 React 19 的核心功能——这一范式转变将渲染逻辑从客户端转移到服务器端,从而实现了性能优化,并加强了服务器端与客户端代码之间的集成。

然而,这种架构演进引入了一个关键的新攻击面。2025年11月29日,安全研究员拉克兰·戴维森(Lachlan Davidson)向Meta的漏洞赏金计划报告了React服务器端反序列化逻辑中存在的一个漏洞。该漏洞于2025年12月3日以CVE-2025-55182的编号公开披露,攻击者仅需发送一个精心构造的HTTP请求,即可实现未经身份验证的远程代码执行。 该漏洞被归类为CWE-502(不受信任数据的反序列化),无需身份验证、用户交互或特殊应用配置——任何为生产环境构建的默认create-next-app部署均可立即被利用。

图1:CVE-2025-55182(来源:NVD)

该漏洞的影响立竿见影且十分严重。在漏洞公开披露后的48小时内,已观察到多起实际利用攻击活动。据Shadowserver基金会称,已有超过77,000个公共IP地址被识别为可能存在漏洞。 Cloudflare的遥测数据显示,在漏洞披露后的那一周内,记录到了超过5.82亿次攻击尝试,平均每小时有超过3,500个独立源IP发起攻击,同时在线攻击IP数峰值达到16,585个。Wiz Research报告称,39%的云环境中存在受影响的实例。 美国网络安全与基础设施安全局(CISA)于2025年12月5日将该漏洞添加至其“已知被利用漏洞”(KEV)目录中。

威胁行为者的行动速度惊人且手段多样。趋势科技记录了多起攻击活动——包括“emerald”和“nuts”僵尸网络活动——这些活动部署了Cobalt Strike信标、Sliver植入程序、Nezha监控代理、快速反向代理(FRP)隧道,以及一种名为“Secret-Hunter”的新型有效载荷,该载荷利用TruffleHog和Gitleaks等开源凭证窃取工具实施攻击。 谷歌Threat Intelligence 识别出多个与中国相关的威胁集群(UNC6600、UNC6586、UNC6588、UNC6603),这些集群部署了专用工具——包括MINOCAT隧道工具、SNOWLIGHT下载器、COMPOOD后门和HISONIC后门——并与伊朗相关行为体以及开展加密货币挖矿活动的以经济利益为动机的团体协同行动。 亚马逊网络服务(AWS)记录显示,早在12月4日,即完整概念验证代码公开之前,与中国相关的团伙就已经开始测试漏洞利用代码。

关于 ReactServer

React 是一个用于构建用户界面的 JavaScript 库,由 Meta 及广泛的开源社区共同维护。 React 19 引入的 ReactServer (RSC)标志着 React 应用程序处理渲染方式的根本性转变。与完全在浏览器中执行的传统客户端组件不同,服务器组件在服务器端执行,生成 UI 的序列化表示,并将其流式传输至客户端。这种设计减少了发送到浏览器的 JavaScript 代码量,提升了交互响应时间指标,并支持直接访问数据库和文件系统等服务器端资源。

图 2:ReactServer (RSC)

RSC 依赖于一种名为“Flight”的自定义序列化协议,用于在客户端和服务器之间编码和传输数据。当客户端调用Server (以前称为Server )时,浏览器会使用 Flight 格式将函数参数打包成结构化的 HTTP 请求。 服务器对该有效载荷进行反序列化,执行请求的功能,并将结果流式传输回客户端。这种客户端与服务器之间的紧密耦合(尽管在架构上非常优雅),意味着反序列化逻辑中的任何缺陷都可能造成立竿见影且灾难性的后果,正如 CVE-2025-55182 所示。

该漏洞不仅影响 React 本身,还影响基于 React 构建的整个框架生态系统。 Next.js(该框架曾收到一份独立的安全公告 CVE-2025-66478,后因重复被驳回)、React Router、Waku、Parcel 的 RSC 插件、Vite 的 RSC 插件以及 RedwoodSDK 均受到影响。即使应用程序未显式定义Server ,只要框架中启用了 RSC 支持,也可能存在漏洞。

技术背景

在分析该漏洞之前,需要先了解支撑该利用链的三个基础概念:JavaScript 中 `await` 与 `thenable` 对象的交互行为、原型链遍历,以及 React Flight 协议的基于数据块的数据模型。

JavaScript 的 await 和 Thenable 对象

`await` 运算符会暂停异步函数的执行,直到被等待的表达式解析完成。当`await`遇到原生Promise 时,它会等待其结算并返回已解决的值。不过,`await` 并不要求必须是原生 Promise——任何具有`.then()`方法的对象(称为“thenable”)都会被视为类似 Promise 的构造。

当 `await` 遇到一个 `thenable` 时,它会调用该对象的 `.then()` 方法,并传入系统提供的 `resolve` 和 `reject` 回调函数。传递给 `resolve` 的值将成为 `await` 表达式的结果。 关键在于,如果解析出的值本身也是一个可接续对象,则会递归调用该嵌套对象的 .then() 方法,直到遇到原始值或已解决的 Promise 为止。这种递归解析行为是利用 CVE-2025-55182 漏洞的核心所在。

原型链遍历

每个 JavaScript 对象都维护着指向其原型的内部链接,可通过__proto__属性访问。当访问对象上的某个属性时,JavaScript 引擎会首先检查该对象自身的属性。如果未找到该属性,引擎就会遍历原型链——沿着每个__proto__链接向上遍历——直到找到该属性,或者原型链在 undefined 处终止。

攻击者可以利用这种继承机制,访问超出对象预期作用域的属性。通过在属性访问路径中包含 __proto__,攻击者可以访问应用程序从未打算暴露的内部方法和构造函数。在 JavaScript 中,表达式 obj.__proto__.constructor.constructor 会返回全局的 Function 构造函数,该构造函数可以根据字符串输入创建并执行任意函数。

React Flight 协议与基于数据块的数据模型

When a client invokes a Server Function, the browser sends an HTTP POST request with a multipart/form-data body. Each form field contains a numbered “chunk” of serialized data. The Flight protocol uses special string prefixes to encode data types: $<id> references the resolved value of another chunk, $@<id> references the raw chunk object itself, $W<id> represents a Set, $K<id> represents FormData, and $B<id> triggers the blob handler.

假设有一个定义如下所示的Server :

图 3:Server 示例

相应的 HTTP 请求包含多个表单字段,每个字段由一个键和一个值组成:字段 0 包含包含诸如"$W1""$K2" 等引用项的参数数组,而字段12_*则包含这些引用所解析出的数据。服务器会在每个字段到达时进行处理,并将中间结果存储在称为“chunks”的对象中。

图 4:调用示例Server 时生成的相应 multipart/form-data HTTP 请求

一个块(chunk)是一个可进行 then 操作的 对象,具有四个关键属性:status(解析状态)、value(存储的数据)、reason(错误信息)和 _response(对父响应对象的反向引用)。 当服务器遇到 await chunk 时,将调用该数据块的.then()方法。如果数据块的状态为 INITIALIZED,则 resolve 回调将接收 chunk.value。如果状态为 PENDING、BLOCKED 或 CYCLIC,则回调将被排队以供稍后执行。

图 5:反序列化过程中的 Chunk 对象状态

Chunk 0 通常表示被调用的Server 参数数组。在接收完所有表单字段并解析完所有内部引用后,chunk_0.value 将包含已完全组装的参数数组,该数组随后会被传递给目标函数。

端到端请求处理(Next.js → React Flight 反序列化)

下文将详细说明 Next.js 在正常情况下如何处理传入的Server 请求,从 HTTP 层一直到 React Flight 反序列化引擎。

图 6:Next.jsServer 请求处理概述

函数 handleAction() - Next.js

当调用Server 时,请求会进入 handleAction 函数。该函数会验证元数据、检查请求头和 CSRF 令牌,并确认该请求是一个有效的 fetch 操作。 随后会创建一个名为busboyStream 的流来解析多部分表单主体。decodeReplyFromBusboy函数将事件发射器绑定到该流上,当接收到原始数据时,会触发 ReactServer反序列化处理函数。decodeReplyFromBusboy 的返回值为chunk_0;await 运算符将其解析,并将组装好的值传递给被调用的Server 。

图 7:handleAction 函数

getChunk 函数返回与给定 ID 对应的数据块。如果该数据块尚不存在,它会创建一个ResolvedModelChunk(如果 response._formData 中已有数据),或者创建一个 PendingChunk(如果该 ID 的数据尚未到达)。

图 8:getChunk 函数

当 decodeReplyFromBusboy 返回 chunk_0 时,该分块仍处于 PENDING 状态。await 运算符会调用 chunk_0.then(),并将 resolve 和 reject 回调函数临时存储在 chunk_0.value 和 chunk_0.reason 中。一旦引用解析完成,wakeChunk 函数会重新激活这些回调函数。

图 9:wakeChunk 函数

反序列化过程 - ReactServer

当 busboyStream 接收到完整的原始数据字段时,它会触发“field”事件发射器,调用 resolveField 函数,并启动反序列化过程——即将原始表单数据转换为完整的 JavaScript 对象。以下函数负责管理此过程。

resolveField(response, key, value)

图 10:resolveField 函数

键和值会被追加到 response._formData 中。随后,该函数会检索与该键对应的 ID 所对应的数据块。如果该数据块已存在,则会调用 resolveModelChunk 来重建它。这种延迟解析是必要的,因为值中可能包含对某些字段的引用,而这些字段的原始数据尚未到达;在这种情况下,ReactServer 一个带有自定义 resolve 和 reject 回调的 PendingChunk,以便稍后处理这些引用。

resolveModelChunk(chunk, value, id)

图 11. resolveModelchunk 函数

resolveModelChunk 会创建一个处于 RESOLVED_MODEL 状态的 ResolvedModelChunk,并注入原始数据。随后,它通过 initializeModelChunk 重建该数据块,并调用 wakeChunk 来触发任何已排队的 resolve 和 reject 回调,从而完成对象或引用的解析。

initializeModelChunk(chunk)

图 12:initializeModelChunk 函数

initializeModelChunk 会将块的状态切换为 CYCLIC(表示正在进行引用解析),并开始反序列化。它使用 JSON.parse 方法根据 chunk.value 构建一个原始 JavaScript 对象,然后将该对象传递给 reviveModel 函数。

reviveModel(响应, 父对象, 父键, 值, 引用)

图 13:reviveModel 函数

reviveModel 会递归处理已解析对象中的每个组件。当遇到字符串值时,它会调用 parseModelString 来处理该值。

parseModelString(response, obj, key, value, reference)

图 14. parseModelString 函数

parseModelString 根据字符串前缀进行分派,以处理不同编码类型的引用。对于以 $ 开头的引用,将调用 getOutlinedModel 函数来解析跨块引用。

getOutlinedModel(response, reference, parentObject, key, map)

图 15:getOutlinedModel 函数

getOutlinedModel 函数会根据“:”分隔符将引用拆分为属性访问路径,然后在目标块对象上遍历该路径以返回被引用的值。如下文“漏洞分析”部分所述,正是由于对这些属性名称缺乏验证,才导致了该漏洞的存在。

脆弱性分析

根本原因

CVE-2025-55182 originates from insufficient input validation in the getOutlinedModel() function within React’s server-side Flight reply handler (ReactFlightReplyServer.js). When a chunk reference includes a property path - such as $<id>:<prop1>:<prop2> - the function resolves it by traversing the specified properties on the target chunk object, computing the result as chunk[prop1][prop2].

图 16:getOutlinedModel() 中的输入验证不足

关键漏洞在于,这些属性名称从未经过验证。 服务器不会验证请求的属性是对象自身的属性,还是继承自原型的属性。因此,攻击者可以在属性路径中包含__proto__,从而遍历原型链并访问那些本不应通过用户控制的输入访问的内部方法。例如,引用 $1:__proto__:then 会被解析为 Chunk.prototype.then——攻击者随后可以使用受控参数调用该函数。

图 17:通过恶意输入对原型进行遍历

脆弱代码

该利用链利用了 React 的 Flight 反序列化逻辑中的两条不同代码路径。

首先是 Chunk.prototype.then,它控制着块作为 thenable 对象的行为。当 await 应用于处于 INITIALIZED 状态的块时,会调用 resolve(chunk.value)。如果 chunk.value 本身是一个 thenable(即具有.then()方法的对象),则 await 运算符会递归调用 chunk.value.then()。正是通过这种递归解析机制,攻击者才能将执行流程重定向到任意函数。

第二个是 parseModelString() 函数中的 $B(blob)前缀处理程序:

图 18:parseModelString 函数中的 $B(blob)

在 $B 情况下,该函数调用了 response._formData.get(response._prefix + id)。_formData.get 和 _prefix 都是存储在片段内的 _response 对象的属性。通过遍历原型链来控制这些属性,攻击者可以将此调用重定向,从而调用全局 Function 构造函数,并将其任意代码作为参数传入。

剥削

Through prototype chain traversal, an attacker reaches the global Function constructor via the path <any_object>.constructor.constructor. Because Chunk.prototype.then is a function, the path $1:constructor:constructor resolves to the global Function constructor, which accepts a string and returns a callable function containing that code.

图 19:Global 函数构造函数

为了展示其潜在的实际影响,我们的研究员构建了一个概念验证有效载荷,该有效载荷与多个安全研究团队独立记录的分析结果一致。该漏洞利用分为两个阶段:

第一阶段——构建伪块:

The object delivered in field 0 acts as a fake chunk. Its then property is set to Chunk.prototype.then via the reference path $1:__proto__:then, allowing the Flight deserialization engine to invoke prototype-level behavior on this attacker-constructed object. The _response._formData.get property is pointed at the global Function constructor via $1:constructor:constructor, and _response._prefix is set to the malicious JavaScript code. The value field contains the string {"then": "$B0"}, instructing the blob handler to invoke itself on the same chunk when resolved. The status field is set to resolved_model so that initializeModelChunk is triggered when .then() is called, causing value to be parsed and the blob handler to fire.

由于此时尚未收到字段 1,ReactServer 会Server 创建 resolve 和 reject 回调函数来处理该待处理的引用。

第二阶段——触发器解析:

一旦字段 1(其中包含“$@0”,即对块 0 的原始引用)被传递,待处理的块就会解析并直接指向该伪块。这会触发 wakeChunk,该方法会处理队列中的回调函数,并在引用解析过程中启动原型链遍历。 一旦伪块完全解析完成,wakeChunk 会被再次调用。由于伪块的解析回调是隐含的 Node.js 解析函数,它会调用该块的 .then() 方法并解析其值——最终通过 Function 构造函数构建并执行注入的恶意代码。

完整的漏洞利用仅需一个HTTP请求:

图 20. 恶意请求

Replacing {{COMMAND}} with any JavaScript code executes it on the server. The reason: -1 field prevents a toString() error during processing. The Next-Action header may contain any arbitrary value - even x - because the vulnerable deserialization occurs before the server validates the requested Server Function. This is what makes the vulnerability pre-authentication: the payload is processed during the deserialization phase, before any application-level authentication or authorization logic is reached.

一旦攻击成功,攻击者便可在服务器上获得完整的 Node.js 执行上下文,包括访问 child_process 以执行 shell 命令、访问包含数据库凭据和API 的环境变量、访问本地文件系统,以及访问支持横向移动的云元数据端点。

概念验证

我们的研究员在受控的实验室环境中,使用通过 create-next-app 生成的标准 Next.js 应用程序(已构建为生产环境版本)成功复现了该漏洞,且未对默认配置进行任何修改。复现结果证实,上述单次请求利用有效载荷能够可靠地实现远程代码执行。

图 21. 存在漏洞的 Next.js Web 应用程序
图 22. 攻击者攻破了存在漏洞的 Next.js 服务器

受控演示表明,攻击者只要能够访问存在漏洞的 Next.js 服务器,即可执行任意 Node.js 代码——包括通过 child_process.exec() 建立反向shell、读取环境变量以及访问本地文件系统——而无需提供任何凭据或触发任何应用程序级别的身份验证检查。 Next-Action 标头接受任意值这一事实进一步证实了该漏洞的预认证性质:服务器会在执行任何操作查找或授权检查之前,先处理并反序列化有效载荷。

缓解

React 团队于 2025 年 12 月 3 日发布了补丁——即该漏洞公开披露的当天。已修复的版本包括 React 19.0.1、19.1.2 和 19.2.1。 该补丁在 getOutlinedModel() 和 reviveModel() 方法中添加了严格的属性验证机制,明确阻止从 Flight 有效负载中用户控制的引用路径解析继承的原型属性——包括 __proto__、constructor 和 prototype。

各组织应立即采取以下措施:

  1. 请根据实际情况运行 npm install react-server-dom-webpack@latest、react-server-dom-parcel@latest 或react-server-dom-turbopack@latest,将 React 包升级至已修复的版本(19.0.1、19.1.2 或 19.2.1)。
  2. 升级框架依赖项——Next.js、React Router、Waku 及其他受影响的框架均已发布相应的补丁。有关具体版本的升级路径,请参阅 React 团队的公告。
  3. 请不要仅依赖托管服务商的缓解措施——尽管Vercel等服务商在漏洞披露后部署了临时WAF规则,但这些只是权宜之计,不能替代对底层软件包的修补。
  4. 检查服务器日志中带有 Next-Action 标头的 POST 请求,这些请求的正文为 multipart/form-data 格式且包含 $@ 或 __proto__ 模式,同时监控应用程序日志中是否存在意外的 child_process 或 execSync 调用。

利用OPSWAT进行缓解

OPSWAT 是 MetaDefender™ 平台的一项专有技术,可提供抵御 CVE-2025-55182 等漏洞所需的可视性和控制能力。 由于该漏洞存在于开源 npm 包(react-server-dom-webpack、react-server-dom-parcel、react-server-dom-turbopack)中,企业必须首先全面盘点这些组件在整个基础设施中的部署情况,才能进行有效的修复。

图 23. SBOM 检测到 CVE-2025-55182

OPSWAT 会生成一份详尽的清单,涵盖所有正在使用的软件组件、库、容器及依赖项。在扫描包含存在漏洞的 React 包的应用程序或容器镜像时,系统会自动将 CVE-2025-55182 标记为“严重”级别,并提供可用修复版本的指导建议,从而使安全团队能够在漏洞被利用之前优先处理并采取补救措施。

OPSWAT 既可在MetaDefender 中使用(用于扫描单个应用程序和容器镜像),也可在MetaDefender Software Chain™ 中使用(用于在整个开发生命周期中实现管道级可视化)。二者结合,可帮助安全团队:

  • 快速定位存在安全漏洞的组件——立即识别出哪些应用程序和容器包含受影响的 react-server-dom-* 包(版本存在安全漏洞),确保不会遗漏任何部署。
  • 确保主动修补漏洞——持续监控开源依赖项,以便在发布新的安全公告时及时发现过时或存在安全隐患的软件包,从而缩短系统暴露在风险中的时间。
  • 确保合规性与供应链透明度——通过维护所有软件组件及其已知漏洞的可审计记录,满足监管要求。

CVE-2025-55182漏洞被利用的速度和规模——仅第一周就出现了超过5.82亿次攻击尝试——充分说明了为何被动修补已不再足够。了解系统中包含哪些组件、它们在何处运行以及何时会暴露漏洞,是主动防御的基础。而这种可视性始于软件物料清单(SBOM)。

参考资料

通过OPSWAT 了解最新信息!

立即注册,即可收到公司的最新动态、 故事、活动信息等。