Appearance
CSRF 跨站伪造请求
概述
CSRF (Cross-Site Request Forgery) 跨站请求伪造是一种攻击方式,攻击者诱导用户在已登录的网站上执行非预期的操作。
攻击原理
CSRF 攻击的核心是:利用用户已登录的身份,伪造用户请求执行恶意操作。
用户登录网站A → 访问攻击者网站B → 网站B自动发送请求到网站A → 网站A执行恶意操作攻击示例
1. GET 请求攻击
场景:银行转账接口使用 GET 请求
html
<!-- 攻击者网站 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" style="display:none">流程:
- 用户登录了银行网站
- 用户访问攻击者网站
- 浏览器自动加载图片,发送 GET 请求
- 银行服务器执行转账操作
2. POST 请求攻击
场景:银行转账接口使用 POST 请求
html
<!-- 攻击者网站 -->
<form action="https://bank.com/transfer" method="POST" id="stealForm">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>
document.getElementById('stealForm').submit()
</script>3. 链接攻击
场景:诱导用户点击恶意链接
html
<!-- 攻击者发送的邮件 -->
<a href="https://bank.com/transfer?to=attacker&amount=10000">
点击领取红包
</a>攻击条件
CSRF 攻击成功需要满足以下条件:
用户已登录目标网站
- 用户的会话未过期
- 浏览器保存了用户的认证信息
目标网站没有防护措施
- 没有验证请求来源
- 没有使用 CSRF Token
- 没有使用 SameSite Cookie
用户访问了攻击者网站
- 点击了恶意链接
- 访问了包含恶意代码的页面
防御措施
1. CSRF Token
原理:服务器生成随机 Token,要求请求携带该 Token。
服务器端实现:
javascript
// 生成 CSRF Token
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex')
}
// 设置 Token 到 Session
app.get('/form', (req, res) => {
const csrfToken = generateCSRFToken()
req.session.csrfToken = csrfToken
res.render('form', { csrfToken })
})
// 验证 Token
app.post('/submit', (req, res) => {
const { csrfToken } = req.body
const sessionToken = req.session.csrfToken
if (!csrfToken || csrfToken !== sessionToken) {
return res.status(403).send('CSRF token validation failed')
}
// 处理请求
res.send('Success')
})前端实现:
html
<!-- 表单中携带 Token -->
<form action="/submit" method="POST">
<input type="hidden" name="csrfToken" value="{{ csrfToken }}">
<input type="text" name="data">
<button type="submit">提交</button>
</form>javascript
// AJAX 请求携带 Token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
})2. SameSite Cookie
原理:限制 Cookie 只在同站请求中发送。
http
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnlySameSite 属性值:
Strict: 完全禁止跨站发送 CookieLax: 允许部分跨站请求(GET 链接、顶级导航)None: 允许跨站发送(需要配合 Secure)
示例:
http
// 严格模式 - 最安全
Set-Cookie: sessionId=abc123; SameSite=Strict
// 宽松模式 - 平衡安全和可用性
Set-Cookie: sessionId=abc123; SameSite=Lax
// 允许跨站 - 需要配合 Secure
Set-Cookie: sessionId=abc123; SameSite=None; Secure3. 验证 Referer
原理:检查请求来源是否为合法域名。
javascript
// 服务器端验证
app.post('/api/action', (req, res) => {
const referer = req.headers.referer
// 检查 Referer 是否为合法域名
if (!referer || !referer.startsWith('https://yoursite.com')) {
return res.status(403).send('Invalid referer')
}
// 处理请求
res.send('Success')
})注意:
- Referer 可以被伪造
- 某些浏览器或安全软件会阻止 Referer
- 不能作为唯一的防护措施
4. 双重 Cookie 验证
原理:将 Token 同时放在 Cookie 和请求参数中。
javascript
// 前端实现
function getCookie(name) {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
if (parts.length === 2) return parts.pop().split(';').shift()
}
// 发送请求时携带 Cookie 中的 Token
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...data,
csrf_token: getCookie('csrf_token')
})
})javascript
// 服务器端验证
app.post('/api/action', (req, res) => {
const cookieToken = req.cookies.csrf_token
const paramToken = req.body.csrf_token
if (!cookieToken || !paramToken || cookieToken !== paramToken) {
return res.status(403).send('CSRF validation failed')
}
// 处理请求
res.send('Success')
})5. 自定义请求头
原理:跨域请求无法自定义请求头(同源策略)。
javascript
// 前端添加自定义请求头
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(data)
})javascript
// 服务器端验证
app.post('/api/action', (req, res) => {
const requestedWith = req.headers['x-requested-with']
if (!requestedWith || requestedWith !== 'XMLHttpRequest') {
return res.status(403).send('Invalid request')
}
// 处理请求
res.send('Success')
})防护策略对比
| 防护措施 | 安全性 | 兼容性 | 实现难度 |
|---|---|---|---|
| CSRF Token | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 中等 |
| SameSite Cookie | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 简单 |
| 验证 Referer | ⭐⭐⭐ | ⭐⭐⭐ | 简单 |
| 双重 Cookie | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 中等 |
| 自定义请求头 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 简单 |
最佳实践
1. 多重防护
javascript
// 结合多种防护措施
app.post('/api/action', (req, res) => {
// 1. 验证 CSRF Token
const csrfToken = req.headers['x-csrf-token']
if (!validateCSRFToken(csrfToken)) {
return res.status(403).send('Invalid CSRF token')
}
// 2. 验证 Referer
const referer = req.headers.referer
if (!validateReferer(referer)) {
return res.status(403).send('Invalid referer')
}
// 3. 处理请求
res.send('Success')
})2. 关键操作二次验证
javascript
// 重要操作要求输入密码或验证码
app.post('/api/transfer', (req, res) => {
const { password, ...transferData } = req.body
// 验证密码
if (!validatePassword(req.user, password)) {
return res.status(403).send('Invalid password')
}
// 执行转账
executeTransfer(transferData)
res.send('Success')
})3. 设置合理的 Cookie 属性
http
Set-Cookie: sessionId=abc123;
HttpOnly;
Secure;
SameSite=Strict;
Path=/;
Domain=yoursite.com4. 使用框架内置防护
javascript
// Express - csurf 中间件
const csrf = require('csurf')
app.use(csrf({ cookie: true }))
// Laravel - 内置 CSRF 验证
// 自动验证所有 POST 请求
// Django - 内置 CSRF 中间件
// 自动为表单添加 CSRF TokenCSRF vs XSS
| 特性 | CSRF | XSS |
|---|---|---|
| 攻击方式 | 伪造请求 | 注入脚本 |
| 是否需要用户登录 | 是 | 否 |
| 是否可以窃取 Cookie | 否 | 是 |
| 防护措施 | CSRF Token | 输出编码 |
| 危害程度 | 中等 | 高 |