DOM 型 XSS
DOM 型 XSS 的基本概念
DOM 型 XSS 是一种特殊类型的跨站脚本攻击,其恶意代码的执行完全发生在客户端的 DOM 环境中,不经过服务器端处理。与反射型或存储型 XSS 不同,DOM 型 XSS 的漏洞根源在于前端 JavaScript 代码对用户输入的不安全处理。
// 典型漏洞示例
const userInput = window.location.hash.substring(1);
document.getElementById("output").innerHTML = userInput;
在这个例子中,攻击者可以构造类似 #<img src=x onerror=alert(1)>
的 URL,当用户访问时就会执行任意 JavaScript 代码。
漏洞产生原理
DOM 型 XSS 的产生通常涉及以下几个关键环节:
-
污染源(Sources):用户可控的输入点
document.URL
document.location
document.referrer
window.name
localStorage/sessionStorage
- 通过
postMessage
接收的数据
-
传播途径(Propagation):不安全的 API 使用
innerHTML/outerHTML
document.write/document.writeln
eval/setTimeout/setInterval
动态执行location.href/location.assign
-
执行点(Sinks):最终触发代码执行的上下文
- HTML 解析上下文
- JavaScript 执行上下文
- URL 上下文
常见危险场景分析
动态内容插入
// 危险示例:直接插入未转义内容
function showSearchResults(query) {
const results = searchDatabase(query);
document.getElementById("results").innerHTML =
`<div>您搜索的 "${query}" 结果如下:</div>`;
}
攻击者可以提交 "><script>alert(1)</script>
这样的查询参数来注入脚本。
基于 URL 的 XSS
// 从URL片段中获取参数
const token = window.location.hash.slice(1);
document.write(`<input type="hidden" value="${token}">`);
如果 URL 包含 #"><script>alert(1)</script>
,就会导致 XSS 漏洞。
JSONP 回调漏洞
function handleResponse(data) {
document.getElementById("user-data").innerHTML = data.userInfo;
}
const script = document.createElement("script");
script.src = "https://api.example.com/user?callback=handleResponse";
document.body.appendChild(script);
如果 API 返回 {"userInfo": "<img src=x onerror=alert(1)>"}
,就会触发 XSS。
现代前端框架中的 DOM XSS
即使是 React、Vue 等现代框架,如果使用不当也会存在 DOM XSS 风险:
React 中的危险
// 危险的使用方式
function UserProfile({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
}
Vue 中的 v-html
<!-- 潜在XSS风险 -->
<template>
<div v-html="userProvidedContent"></div>
</template>
Angular 的 bypassSecurityTrust
// 不安全的使用方式
constructor(private sanitizer: DomSanitizer) {}
displayContent() {
this.trustedContent = this.sanitizer.bypassSecurityTrustHtml(userInput);
}
防御策略与技术
输入验证与净化
// 使用DOMPurify进行HTML净化
import DOMPurify from "dompurify";
const clean = DOMPurify.sanitize(dirtyInput, {
ALLOWED_TAGS: ["b", "i", "em", "strong"],
ALLOWED_ATTR: ["style"]
});
安全的 DOM 操作方式
// 使用textContent代替innerHTML
element.textContent = userInput;
// 使用DOM API创建元素
const div = document.createElement("div");
div.appendChild(document.createTextNode(userInput));
内容安全策略(CSP)
<!-- 严格的CSP策略 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline'">
现代框架的最佳实践
React 示例:
// 安全的使用方式
function SafeComponent({ text }) {
return <div>{text}</div>;
}
Vue 示例:
<!-- 安全的内容绑定 -->
<template>
<div>{{ userContent }}</div>
</template>
高级攻击手法与防御
基于原型链污染的 XSS
// 攻击者可能污染原型链导致XSS
Object.prototype.innerHTML = "<script>alert(1)</script>";
document.body.appendChild(document.createElement("div"));
防御方法:
// 冻结Object.prototype
Object.freeze(Object.prototype);
// 使用hasOwnProperty检查
if (element.hasOwnProperty("innerHTML")) {
element.innerHTML = safeContent;
}
SVG 文件中的 XSS
<!-- 恶意SVG文件示例 -->
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(1)</script>
</svg>
防御策略:
- 服务器设置正确的 Content-Type
- 使用专门的 SVG 净化库
- 避免直接渲染用户上传的 SVG
Web Worker 中的 XSS
// Worker中也可能存在XSS风险
const workerCode = `onmessage=function(e){eval(e.data)}`;
const blob = new Blob([workerCode]);
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage("alert(1)");
安全做法:
// 使用结构化克隆算法传递数据
worker.postMessage({ command: "render", data: sanitizedHTML });
自动化检测工具
-
静态分析工具:
- ESLint 插件:eslint-plugin-security
- Semgrep 规则库
-
动态检测工具:
- OWASP ZAP
- Burp Suite DOM Invader
- Chrome DevTools 的 XSS 审计功能
-
单元测试方案:
// 使用Jest测试XSS防护
test("sanitize function prevents XSS", () => {
const malicious = "<script>alert(1)</script>";
expect(sanitize(malicious)).not.toContain("<script>");
});
实际案例分析
案例1:单页应用路由 XSS
// 不安全的实现
router.beforeEach((to, from, next) => {
document.title = to.query.title || "Default Title";
next();
});
攻击者可以构造 ?title=<script>alert(1)</script>
的 URL。
修复方案:
// 安全实现
router.beforeEach((to, from, next) => {
document.title = sanitizeTitle(to.query.title) || "Default Title";
next();
});
function sanitizeTitle(str) {
return String(str).replace(/[<>]/g, "");
}
案例2:富文本编辑器漏洞
// 不安全的富文本处理
editor.on("save", (content) => {
saveToDatabase(content);
preview.innerHTML = content;
});
防御方案应包含:
- 服务器端净化
- iframe 沙箱隔离
- 严格的 CSP 策略
- 纯文本预览选项
浏览器安全机制
- Trusted Types API:
// 启用Trusted Types
if (window.trustedTypes && trustedTypes.createPolicy) {
const escapePolicy = trustedTypes.createPolicy("escapePolicy", {
createHTML: (str) => str.replace(/</g, "<")
});
}
- XSS Auditor(已弃用):
- Chrome 曾实现的反射型 XSS 防护
- 被 CSP 取代
- 跨源隔离:
// 启用跨源隔离
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
性能与安全的平衡
// 安全但性能较差的做法
function safeButSlow(content) {
const temp = document.createElement("div");
temp.textContent = content;
return temp.innerHTML;
}
// 平衡方案
const escapeMap = {
"<": "<",
">": ">",
// ...其他需要转义的字符
};
function fastEscape(str) {
return String(str).replace(/[<>]/g, (m) => escapeMap[m]);
}
相关漏洞模式扩展
- DOM Clobbering:
<!-- 通过DOM污染全局变量 -->
<form id="xss"><input name="innerHTML" value="<script>alert(1)</script>"></form>
<script>
// xss.innerHTML 已被污染
document.body.appendChild(xss);
</script>
- jQuery 中的 XSS:
// 不安全的jQuery用法
$("#container").html(userControlledInput);
// 安全用法
$("#container").text(userControlledInput);
- 动态脚本加载:
// 不安全的动态脚本
const script = document.createElement("script");
script.src = untrustedURL;
document.body.appendChild(script);
安全替代方案:
// 使用import()实现动态加载
import(/* webpackIgnore: true */ trustedURL)
.catch(() => { /* 错误处理 */ });
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:存储型 XSS(持久型)
下一篇:XSS 攻击的危害与影响