◈ 渗透任务 原型链污染漏洞验证 任务中
你的身份 「暗影安全公司」渗透测试工程师 目标网站 MergeLib 工具库在线演示 (demo.mergelib.example.com) 委托方 MergeLib 使用的深合并函数存在原型污染漏洞,可能导致 XSS 任务目标 通过 URL 参数污染 Object.prototype,控制脚本加载源

◈ 教程:Prototype Pollution

什么是原型链?

JavaScript 中每个对象都继承自 Object.prototype。当你访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript 会沿着原型链向上查找,直到找到该属性或到达原型链的末端。

核心原理:原型污染是指攻击者向 Object.prototype 添加恶意属性。由于所有对象都继承自 Object.prototype,被污染的属性会被所有对象"继承"。

原型链结构

const obj = { a: 1 };
// obj → Object.prototype → null

// 访问属性时的查找顺序:
obj.a          // 找到:obj 自身有 a
obj.toString   // 找到:Object.prototype 有 toString
obj.malicious  // 未找到:返回 undefined

// 如果 Object.prototype 被污染:
Object.prototype.malicious = 'hacked';
obj.malicious  // 找到:返回 'hacked'

污染来源(Source)

常见的原型污染来源:

  • JSON.parse — 解析用户提供的 JSON 数据
  • URL 参数 — 通过查询字符串传入嵌套对象
  • 合并函数 — 深拷贝/深合并时未过滤 __proto__
  • cookie / localStorage — 存储的用户数据

常见的漏洞合并函数

// 危险的深合并 — 不检查 __proto__
function deepMerge(target, source) {
    for (let key in source) {
        if (typeof source[key] === 'object') {
            if (!target[key]) target[key] = {};
            deepMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
    return target;
}

// 调用示例:
const malicious = JSON.parse('{"__proto__":{"isAdmin":true}}');
const config = {};
deepMerge(config, malicious);

// 此时所有对象都被污染:
console.log({}.isAdmin); // true

污染汇聚点(Sink)

原型污染本身不直接执行代码,需要一个"汇聚点"来触发 XSS:

innerHTML / document.createElement
// 如果代码读取配置中的 URL 并创建脚本
if (config.src) {
    const s = document.createElement('script');
    s.src = config.src;  // 被污染后,src 存在
    document.body.appendChild(s);
}
eval / Function
// 如果代码使用配置中的字符串进行 eval
eval(config.code || 'console.log("safe")');
// 被污染后:eval('alert(1)')
jQuery 的 $() 选择器
// jQuery < 3.5.0
$('
' + config.html + '
'); // 被污染后可注入 HTML

真实 CVE 案例

  • CVE-2022-21824 — Node.js prototype pollution via querystring
  • CVE-2021-25926 — axios 原型污染
  • CVE-2020-28477 — lodash merge 原型污染
  • CVE-2019-10744 — lodash defaultsDeep 原型污染

防御方法

// 方法 1:冻结 Object.prototype
Object.freeze(Object.prototype);

// 方法 2:过滤危险键
function safeMerge(target, source) {
    for (let key in source) {
        if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
            continue; // 跳过危险键
        }
        // ... 合并逻辑
    }
}

// 方法 3:使用 Object.create(null) 创建无原型对象
const obj = Object.create(null); // 没有原型链

◈ 练习:Prototype Pollution

目标:页面使用了一个存在漏洞的深合并函数处理 URL 参数。合并后的配置会被用于动态加载脚本。请通过原型污染让 window.appConfig.settings.src 指向恶意脚本。
当前 URL 参数:
loading...
appConfig 状态:
loading...
◈ 查看漏洞源码 (JavaScript)
// 漏洞深合并函数 — 未检查 __proto__ function deepMerge(target, source) { for (let key in source) { if (typeof source[key] === 'object' && source[key] !== null) { if (!target[key]) target[key] = {}; deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } return target; } // 解析 URL 参数 const params = new URLSearchParams(location.search); const config = {}; for (const [key, value] of params) { const keys = key.split('.'); let obj = config; for (let i = 0; i < keys.length - 1; i++) { if (!obj[keys[i]]) obj[keys[i]] = {}; obj = obj[keys[i]]; } obj[keys[keys.length - 1]] = value; } // 漏洞合并到全局配置 deepMerge(window.appConfig, config); // Sink: 从配置读取并创建 script if (window.appConfig.settings && window.appConfig.settings.src) { const s = document.createElement('script'); s.src = window.appConfig.settings.src; document.body.appendChild(s); }
◈ 过滤状态:
  • URL 参数解析 — 无过滤
  • deepMerge 函数 — 未检查 __proto__
  • 脚本加载 sink — 读取 appConfig.settings.src

提示:尝试在 URL 中添加 ?__proto__[settings][src]=...