本文目录导读:

DOM型XSS(基于DOM的跨站脚本攻击)的规避需要从输入源头、数据流转、输出编码三个层面进行防御,核心原则是永远不要信任用户输入或任何不可控的数据源,以下是具体的规避策略和代码实践:
核心防御:输出转义与编码(最关键)
DOM型XSS的本质是不可信数据被当作HTML/JavaScript代码执行,在将数据插入到HTML、属性、JavaScript代码或URL前,必须进行上下文感知的编码。
1 避免直接操作危险API
🚫 高危操作(应完全避免):
// 1. innerHTML - 直接解析HTML
element.innerHTML = userData;
// 2. document.write / writeln
document.write('<script>alert(1)</script>');
// 3. eval() / setTimeout(string) / setInterval(string)
eval(userData);
// 4. location / href - 直接设置
window.location = userInput;
✅ 安全替代方案(优先使用):
// 使用textContent(自动转义HTML标签)
element.textContent = userData;
// 使用innerText(会考虑CSS样式,性能稍差)
element.innerText = userData;
// 创建DOM节点(最安全)
const p = document.createElement('p');
p.textContent = userData;
document.body.appendChild(p);
2 正确使用编码函数
当必须动态生成HTML时,使用encodeURI或自定义转义函数:
// HTML实体编码(防HTML注入)
function escapeHtml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
// JavaScript字符串编码(防eval注入)
function escapeJs(str) {
return str.replace(/[\\"']/g, function(match) {
if (match === '\\') return '\\\\';
if (match === '"') return '\\"';
if (match === "'") return "\\'";
});
}
// URL编码(防location注入)
function escapeUrl(str) {
return encodeURIComponent(str);
}
输入源控制:数据获取时的严格校验
DOM型XSS的输入源通常来自URL参数、Hash、localStorage、postMessage、Reflect等,必须对来源数据进行格式净化。
1 URL参数净化(location.search)
// 从URL获取参数
function getParam(name) {
const urlParams = new URLSearchParams(window.location.search);
const value = urlParams.get(name);
// 校验:只允许字母数字
if (!/^[\w-]+$/.test(value)) {
return ''; // 或抛异常
}
return value;
}
2 Hash净化(location.hash)
// 只允许纯文本(如锚点导航)
function safeHash() {
const hash = decodeURIComponent(window.location.hash.slice(1));
// 校验:只允许允许的字符
if (!/^[a-zA-Z0-9_-]+$/.test(hash)) {
return '';
}
return hash;
}
3 postMessage数据校验
window.addEventListener('message', (event) => {
// 1. 验证来源(origin)
if (event.origin !== 'https://trusted-site.com') {
return;
}
// 2. 验证数据格式
if (typeof event.data !== 'string') {
return;
}
// 3. 安全处理数据
document.getElementById('output').textContent = event.data;
});
高级防御:内容安全策略(CSP)与沙箱
CSP是浏览器层面的终极防线,即使开发有漏洞,CSP也能阻止XSS执行。
1 设置严格的CSP头部
服务器HTTP响应头:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'
- 禁止内联脚本:移除
'unsafe-inline',强制所有JS通过外部文件加载。 - 禁止eval:不添加
'unsafe-eval'。 - 限制来源:仅允许加载同域资源。
2 使用nonce或hash(当必须内联时)
若必须使用内联脚本(如某些框架),用nonce值限制:
<!-- 服务端生成随机nonce -->
<script nonce="abc123">
// 安全的内联代码
</script>
CSP头部:
Content-Security-Policy: script-src 'nonce-abc123'
3 DOM隔离:沙箱iframe的渲染,放入沙箱iframe中:
<iframe sandbox="allow-scripts allow-same-origin" src="about:blank"></iframe>
// 在沙箱中安全显示用户内容 iframe.contentDocument.body.textContent = userData;
特定场景规避:框架与API安全使用
1 jQuery安全使用(常见误用)
🚫 高危:
$('#output').html(userData); // 同innerHTML
$('<div>'+userData+'</div>'); // 字符串拼接创建元素
$('#link').attr('href', userData); // 可注入javascript:协议
✅ 安全方式:
$('#output').text(userData); // 替换为text()
$('<div>').text(userData); // 创建节点再入DOM
$('#link').prop('href', userData); // 使用prop替代attr
2 DOM clobbering(DOM覆盖面)规避
防止攻击者通过id或name属性覆盖全局变量:
// 检查变量是否存在
if (typeof window.userConfig === 'object' && window.userConfig !== null) {
// 安全访问
}
// 使用let/const而不是var(也不会被DOM覆盖)
3 模板安全(如Vue/React)
现代框架默认有textContent绑定,但需注意:
// React:使用{}插值会自动转义
<div>{userData}</div> // ✅ 安全
// React:dangerouslySetInnerHTML需手动清理
<div dangerouslySetInnerHTML={{__html: sanitize(userData)}} />
// Vue:使用{{}}会自动转义
<div>{{ userData }}</div> // ✅ 安全
// Vue:v-html需清理
<div v-html="sanitize(userData)" />
工具与自动化检测
- 动态检测:使用
DOMPurify库对不可信HTML进行清理:const cleanHtml = DOMPurify.sanitize(unsafeHtml); element.innerHTML = cleanHtml; // 此时安全
- 静态扫描:使用ESLint插件
eslint-plugin-no-unsanitized检测innerHTML使用。 - 浏览器测试:Chrome DevTools的Security面板可查看CSP违规报告。
避坑清单
| 操作 | 风险等级 | 正确做法 |
|---|---|---|
element.innerHTML = x |
🚨高危 | 改用textContent或DOMPurify |
window.location = x |
🚨高危 | 仅允许白名单URL + encodeURI |
eval(x) |
🔴禁用 | 使用JSON.parse(如果目标是对象) |
document.write(x) |
🔴禁用 | 使用DOM API创建节点 |
new Function(x) |
🔴禁用 | 同上 |
setTimeout(x, 0) (字符串) |
🔴禁用 | 传入函数而非字符串 |
postMessage接收数据 |
🟡中危 | 校验origin + 数据格式 |
jQuery .html(x) |
🚨高危 | 替换为.text(x) |
| URL参数直接使用 | 🚨高危 | 正则校验 + 类型检查 |
核心原则:任何从URL、Hash、localStorage、postMessage、Cookie、前端数据库等来源获取的数据,在输出到HTML、JS、URL、属性前,都必须进行上下文感知的转义或使用安全的API操作。