CSRF 攻击:原理与防御
理解 CSRF 攻击原理,掌握 SameSite、Token、Referer 三种防御方式
问题
什么是 CSRF(跨站请求伪造)攻击?如何防御?
解答
CSRF 攻击原理
CSRF 利用用户已登录的身份,在用户不知情的情况下,诱导浏览器向目标网站发送请求。
攻击流程:
1. 用户登录 bank.com,浏览器保存了 Cookie
2. 用户访问恶意网站 evil.com
3. evil.com 页面包含一个自动提交的表单,指向 bank.com/transfer
4. 浏览器自动携带 bank.com 的 Cookie 发送请求
5. bank.com 无法区分这是用户主动操作还是被诱导的
恶意页面示例:
<!-- evil.com 的页面 -->
<form action="https://bank.com/transfer" method="POST" id="hack">
<input type="zzinb" name="to" value="attacker" />
<input type="zzinb" name="amount" value="10000" />
</form>
<script>
// 页面加载后自动提交
document.getElementById('hack').submit()
</script>
防御方式一:SameSite Cookie
通过设置 Cookie 的 SameSite 属性,限制跨站请求携带 Cookie。
// Node.js Express 设置 Cookie
res.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'Strict' // 或 'Lax'
})
SameSite 取值:
- Strict: 完全禁止跨站携带,最安全但影响用户体验
- Lax: 允许导航类请求(链接跳转)携带,禁止 POST/iframe/AJAX
- None: 不限制,必须配合 Secure 使用
防御方式二:CSRF Token
服务端生成随机 Token,前端请求时携带,服务端验证。
// 服务端:生成并存储 Token
const crypto = require('crypto')
function generateCsrfToken(session) {
const token = crypto.randomBytes(32).toString('hex')
session.csrfToken = token
return token
}
// 服务端:验证 Token 的中间件
function verifyCsrfToken(req, res, next) {
const token = req.headers['x-csrf-token'] || req.body._csrf
if (!token || token !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token 验证失败' })
}
next()
}
// 前端:从页面或接口获取 Token,请求时携带
const csrfToken = document.querySelector('meta[name="csrf-token"]').content
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken // 携带 Token
},
body: JSON.stringify({ to: 'friend', amount: 100 })
})
防御方式三:Referer / Origin 校验
检查请求来源是否为可信域名。
// 服务端中间件:校验请求来源
function checkOrigin(req, res, next) {
const origin = req.headers.origin || req.headers.referer
const allowedOrigins = ['https://bank.com', 'https://www.bank.com']
if (!origin) {
// 某些情况下可能没有 Origin/Referer,根据业务决定是否放行
return res.status(403).json({ error: '缺少来源信息' })
}
const isAllowed = allowedOrigins.some(allowed => origin.startsWith(allowed))
if (!isAllowed) {
return res.status(403).json({ error: '非法请求来源' })
}
next()
}
综合防御示例
const express = require('express')
const session = require('express-session')
const crypto = require('crypto')
const app = express()
// Session 配置
app.use(session({
secret: 'your-secret-key',
cookie: {
httpOnly: true,
secure: true,
sameSite: 'Lax' // 第一道防线
}
}))
// CSRF Token 中间件
app.use((req, res, next) => {
// GET 请求时生成 Token
if (req.method === 'GET') {
req.session.csrfToken = crypto.randomBytes(32).toString('hex')
}
next()
})
// 需要保护的接口
app.post('/api/transfer', (req, res) => {
// 验证 Token
const token = req.headers['x-csrf-token']
if (token !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' })
}
// 验证 Origin
const origin = req.headers.origin
if (origin !== 'https://bank.com') {
return res.status(403).json({ error: 'Invalid origin' })
}
// 处理业务逻辑
res.json({ success: true })
})
关键点
- CSRF 本质:利用浏览器自动携带 Cookie 的特性,伪造用户请求
- SameSite Cookie:从源头阻止跨站请求携带认证信息,推荐设置为
Lax - CSRF Token:服务端生成随机值,前端请求时携带验证,攻击者无法获取
- Referer/Origin 校验:验证请求来源域名,但可能被篡改或缺失
- 防御组合:生产环境建议 SameSite + Token 双重防护
目录