原型污染-并绕过客户端HTML过滤器
字数 1080 2025-08-18 17:33:23
原型污染攻击与客户端HTML过滤器绕过技术分析
1. 原型污染基础
1.1 JavaScript原型继承机制
JavaScript采用基于原型的继承模型,与传统的基于类的继承不同:
- 每个对象都有一个原型(
__proto__或通过Object.getPrototypeOf获取) - 访问对象属性时,JS引擎会沿着原型链向上查找
Object.prototype是所有对象的最终原型(除非显式设置为null)
const obj = { prop1: 111, prop2: 222 };
obj.toString(); // 来自Object.prototype的默认方法
1.2 原型污染原理
通过修改Object.prototype,可以影响所有JavaScript对象的行为:
Object.prototype.admin = true;
const user = { userid: 123 };
if (user.admin) {
console.log('You are an admin'); // 会被执行
}
1.3 原型污染的产生条件
通常由不安全的对象合并操作引起:
function recursiveMerge(obj1, obj2) {
for (let key in obj2) {
if (key in obj1) {
recursiveMerge(obj1[key], obj2[key]);
} else {
obj1[key] = obj2[key];
}
}
}
// 攻击示例
const obj1 = {};
const obj2 = JSON.parse('{"__proto__":{"x":1}}');
recursiveMerge(obj1, obj2); // 污染Object.prototype
关键点:
JSON.parse将__proto__视为普通属性而非原型访问器- 许多流行库(如lodash、jQuery)曾存在此类漏洞
2. 原型污染与HTML过滤器绕过
2.1 HTML过滤器工作原理
HTML过滤器通过白名单机制净化HTML输入,防止XSS攻击:
<!-- 输入 -->
<h1>Header</h1>This is <b>some</b> <i>HTML</i><script>alert(1)</script>
<!-- 输出 -->
<h1>Header</h1>This is <b>some</b> HTML
2.2 白名单实现方式
2.2.1 数组方式(安全)
const ALLOWED_ELEMENTS = ["h1", "i", "b", "div"];
// 原型污染无法影响数组长度或已有索引
2.2.2 对象方式(易受攻击)
const ALLOWED_ELEMENTS = {
"h1": true,
"i": true,
"b": true,
"div": true
};
// 原型污染可添加新属性
Object.prototype.SCRIPT = true; // 绕过检查
3. 主流HTML过滤器分析
3.1 sanitize-html
默认配置:
allowedTags: ['h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'abbr', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe'],
allowedAttributes: {
a: ['href', 'name', 'target'],
img: ['src']
}
绕过方法:
Object.prototype['*'] = ['onload'];
// 允许所有标签的onload属性
防御机制:
- 使用
hasOwnProperty检查属性(但对通配符*无效)
3.2 xss库
绕过方法:
Object.prototype.whiteList = {
img: ['src', 'onerror']
};
// 允许img标签的onerror属性
3.3 DOMPurify
绕过方法1:
Object.prototype.ALLOWED_ATTR = ['onerror', 'src'];
绕过方法2(更隐蔽):
Object.prototype.documentMode = 9; // 禁用过滤器
3.4 Google Closure库
绕过方法:
Object.prototype['* ONERROR'] = 1;
Object.prototype['* SRC'] = 1;
// 允许所有标签的onerror和src属性
4. 原型污染检测工具
4.1 静态分析工具
- 提取代码中所有可能的属性访问标识符
- 将这些属性添加到
Object.prototype - 监控属性访问是否到达原型链
4.2 动态检测方法
通过代码插桩转换属性访问:
// 原始代码
if (cfg.ADD_ATTR) { ... }
// 转换后代码
if ($_GET_PROP(cfg, 'ADD_ATTR')) { ... }
// 检测函数
function $_GET_PROP(obj, prop) {
if (!(prop in obj)) {
console.log(`Possible prototype pollution for ${prop}`);
console.trace();
}
return obj[prop];
}
5. 防御措施
-
冻结原型:
Object.freeze(Object.prototype); -
使用
Object.create(null)创建无原型对象:const safeObj = Object.create(null); -
安全合并函数:
function safeMerge(target, source) { for (const key in source) { if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } return target; } -
使用Map代替普通对象存储白名单
6. 实际攻击案例
-
Google搜索栏XSS:
- 通过原型污染绕过内部HTML过滤器
- 可在搜索结果中执行任意JavaScript
-
Ghost CMS RCE:
- 通过原型污染导致远程代码执行
-
Kibana RCE:
- 利用原型污染实现远程代码执行
7. 总结
原型污染是一种强大的攻击技术,可以:
- 绕过客户端HTML过滤器
- 修改应用程序逻辑
- 导致XSS甚至RCE漏洞
关键防护原则:
- 永远不要信任用户提供的JSON输入
- 使用安全的对象操作函数
- 对关键配置对象使用无原型或冻结的对象
- 定期审计依赖库的安全性