DOM劫持发生于HTML元素的id或name属性被浏览器自动挂载为window全局变量,攻击者通过注入恶意标签(如<img id="alert">)覆盖内置函数或变量,导致后续JS逻辑误读对象类型甚至执行任意代码。

DOM劫持是怎么发生的
HTML元素的 id 或 name 属性会自动挂载为全局变量,这是浏览器默认行为,不是 bug。比如 <input id="username"> 一加载,window.username 就指向这个 DOM 元素——哪怕你后面写了 const username = "admin",也会被覆盖。
攻击者利用这点注入带 id 的恶意标签,就能污染全局作用域。典型例子:<img id="alert" src=x onerror="alert(1)"> 提交后,页面里任何地方调用 alert() 都可能触发执行,因为 window.alert 已被替换成那个 <img> 元素。
- 所有含
id或name的 HTML 标签都参与自动挂载,包括<form>、<iframe>、<button> - 冲突发生在页面解析完成时,JS 执行前,无法靠
let/const声明规避 - 现代框架(React/Vue)默认不暴露全局变量,但纯 HTML + 原生 JS 场景下极易中招
如何阻止 id/name 自动挂载为全局变量
没有标准 API 能关掉这个机制,只能绕过。核心思路是:不让浏览器在初始解析阶段把用户可控的 id 暴露出去。
- 服务端渲染时,对用户输入的
id、name值做前缀混淆,比如统一加user_或哈希后缀:id="user_f8a2b",避免撞上常见变量名 - 前端动态插入内容时,用
document.createElement()构造元素,再用setAttribute('id', userInput),而不是直接拼字符串塞进innerHTML - 若必须用
innerHTML,先用正则剥离所有id=和name=属性(或替换为data-id),再写入 - 禁用
document.write()和document.writeln(),它们会触发二次解析,放大挂载风险
白名单属性为什么不能只防 onerror
只过滤 onerror、onclick 这类事件属性远远不够。浏览器把很多属性值当执行上下文处理,比如 href="javascript:..."、src="data:text/html,..."、formaction="javascript:...",甚至 is="x-evil"(Custom Elements 中可触发构造函数)。
立即学习“前端免费学习笔记(深入)”;
白名单策略必须覆盖三类风险:
-
协议类:只允许
href和src以https://、/、#开头,拒绝javascript:、data:、vbscript: -
样式类:禁用
style属性(expression()、url(javascript:)等绕过方式太多) -
标识类:限制
class和id只能含字母数字和短横线,禁止点号、方括号等 JS 访问符
漏掉任意一类,白名单就形同虚设——CSP 对这类内联执行完全无效。
为什么 CSP 的 script-src 对 DOM 劫持没用
Content-Security-Policy: script-src 'self' 只管脚本加载和执行,不管 DOM 结构本身是否被污染。而 DOM 劫持的核心问题是:变量名被覆盖,导致后续 JS 逻辑误读对象类型。
例如:
const btn = document.getElementById('submit');
btn.addEventListener('click', handler); // 正常
// 但如果用户提交了 <button id="submit"></button>,btn 就是 null 或 HTMLElement,不是预期的 Element 实例
这时候即使没执行任何脚本,业务逻辑也已崩坏。更危险的是,某些库(如 jQuery 旧版)会直接用 window[xxx] 查找元素,一旦被污染,整个链路失控。
真正有效的组合是:
- 服务端净化输入,剥离/转义
id、name、href、src等高危属性 - 前端用
textContent渲染纯文本,富文本场景强制走DOMPurify.sanitize(..., { RETURN_TRUSTED_TYPE: true })+ Trusted Types - CSP 仅作为兜底,防住漏网的脚本执行,不指望它解决变量污染问题
最易被忽略的一点:所有用户输入拼接进 HTML 字符串前,必须明确区分「渲染上下文」——是插进文本节点?属性值?还是 JS 字符串?每种上下文对应不同编码规则,混用等于放弃防御。



















