XSS攻击类型与防御方案
约 1461 字大约 5 分钟
xsssecurity
2025-08-14
概述
跨站脚本攻击(Cross-Site Scripting, XSS)允许攻击者在受害者的浏览器中执行恶意脚本。XSS 是 Web 应用中最常见的安全漏洞之一,可导致会话劫持、数据窃取、钓鱼攻击和恶意软件传播。
XSS 攻击类型
存储型 XSS(最危险)
# 漏洞示例:用户评论未经过滤直接存储和显示
@app.route('/comment', methods=['POST'])
def add_comment():
comment = request.form['comment']
# 直接存储用户输入(危险)
db.execute("INSERT INTO comments (body) VALUES (?)", (comment,))
return redirect('/comments')
@app.route('/comments')
def show_comments():
comments = db.execute("SELECT body FROM comments").fetchall()
html = ""
for c in comments:
# 未编码输出 — 攻击者可以注入脚本标签
html += f"<div class='comment'>{c['body']}</div>"
return html反射型 XSS
# 漏洞示例:搜索参数直接反射到页面
@app.route('/search')
def search():
query = request.args.get('q', '')
# 攻击者可在 URL 的 q 参数中构造恶意脚本
return f"<h1>搜索结果: {query}</h1>"DOM 型 XSS
DOM XSS 完全在客户端发生。攻击者通过控制 DOM 的数据源(source),将恶意内容注入到危险的 DOM 操作中(sink)。
常见的危险 source:location.hash、location.search、document.referrer、window.name、postMessage 数据。
常见的危险 sink:innerHTML 赋值、insertAdjacentHTML、document.write() 调用等。
// 安全替代方案:使用 textContent 而非 innerHTML
const hash = location.hash.substring(1);
document.getElementById('content').textContent = hash; // 安全:自动编码为文本上下文感知编码
不同的 HTML 上下文需要不同的编码策略。
HTML 上下文编码
import html
# Python 内置 HTML 转义
safe_output = html.escape(user_input)
# < -> < > -> > & -> & " -> " ' -> '
# Flask/Jinja2 自动转义
# 模板中 {{ variable }} 默认自动转义
# {{ variable|safe }} 禁用转义(危险,需确保数据安全)// JavaScript 中使用 textContent 而非 innerHTML
element.textContent = userInput; // 安全:自动编码为文本
// 如果确实需要操作 HTML,先编码
function escapeHtml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}JavaScript 上下文编码
// 在 JavaScript 字符串中插入用户数据
// 正确做法:使用 JSON.stringify
const safeValue = JSON.stringify(userInput);
// JSON.stringify 会转义引号、反斜杠和特殊字符URL 上下文编码
// URL 参数编码
const safeUrl = `/search?q=${encodeURIComponent(userInput)}`;
// 防御 javascript: 伪协议
function sanitizeUrl(url) {
try {
const parsed = new URL(url, window.location.origin);
if (!['http:', 'https:', 'mailto:'].includes(parsed.protocol)) {
return '#'; // 拒绝非安全协议
}
return url;
} catch {
return '#';
}
}DOMPurify 净化库
当必须渲染用户提供的 HTML 时(如富文本编辑器内容),使用 DOMPurify 净化。
import DOMPurify from 'dompurify';
const dirty = '<p>Hello</p><script>alert("xss")</script><img src=x onerror=alert(1)>';
const clean = DOMPurify.sanitize(dirty);
// 结果: '<p>Hello</p><img src="x">'
// script 标签和 onerror 事件被移除
// 自定义配置
const clean2 = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false,
});Trusted Types API
Trusted Types 是浏览器原生的 DOM XSS 防御机制,通过 CSP 头启用。
// 通过 CSP 启用: trusted-types myPolicy; require-trusted-types-for 'script'
if (window.trustedTypes && trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy('myPolicy', {
createHTML: (input) => DOMPurify.sanitize(input),
createScriptURL: (input) => {
const url = new URL(input, document.baseURI);
if (url.origin === location.origin) return input;
throw new TypeError('Untrusted URL');
},
});
// 使用策略创建受信任的 HTML
const trustedHtml = policy.createHTML(userContent);
// 不通过策略的 innerHTML 赋值会被浏览器阻止
}HttpOnly Cookie
# 设置 HttpOnly 防止 JavaScript 读取 Cookie
response.set_cookie(
'session_id',
value='abc123',
httponly=True, # JavaScript 无法通过 document.cookie 访问
secure=True, # 仅 HTTPS
samesite='Lax', # 跨站请求限制
max_age=3600,
)框架层防御
React
// React 默认转义所有插值
const userInput = '<script>alert(1)</script>';
return <div>{userInput}</div>;
// 渲染为文本,不执行脚本
// 对于 href 中的 javascript: 协议需要手动验证
function SafeLink({ url, children }) {
const safeUrl = sanitizeUrl(url);
return <a href={safeUrl}>{children}</a>;
}Vue
<template>
<!-- 安全:双花括号自动转义 -->
<div>{{ userInput }}</div>
<!-- v-html 需要先净化 -->
<div v-html="sanitizedHtml"></div>
</template>
<script setup>
import DOMPurify from 'dompurify';
import { computed } from 'vue';
const props = defineProps({ rawHtml: String });
const sanitizedHtml = computed(() => DOMPurify.sanitize(props.rawHtml));
</script>防御层次总结
最佳实践
- 使用框架的自动编码(React JSX、Vue 双花括号、Jinja2 自动转义)
- 根据输出上下文选择正确的编码,HTML/JS/URL/CSS 上下文各不相同
- 必须渲染 HTML 时使用 DOMPurify,不要自己实现 HTML 过滤
- 部署严格的 CSP,使用 nonce + strict-dynamic
- Cookie 设置 HttpOnly + Secure + SameSite
- 使用 textContent 而非 innerHTML,除非确实需要渲染 HTML
- 验证所有 URL 的 scheme,拒绝
javascript:和data:协议 - 启用 Trusted Types 作为 DOM XSS 的终极防线
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于