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