CSRF攻击与防御深度解析:从SameSite Cookie到Token验证的攻防实战
CSRF 跨站请求伪造是前端安全必考题:从攻击链路讲清 SameSite Cookie、Token 校验、Referer 校验等防御手段。 掌握后能现场画出「带 Cookie 的跨站请求如何被伪造与拦截」。
一句话概括
CSRF(跨站请求伪造)利用浏览器自动携带Cookie的机制,在用户不知情时以用户身份发起恶意请求;防御的核心是在”浏览器自动发送的请求”和”用户主动发起的请求”之间建立区分。
核心知识点
1. CSRF 的攻击流程(一句话版)
用户在 bank.com 登录后,Cookie 被浏览器保存。攻击者构造一个页面,其中包含指向 bank.com/transfer?amount=10000&to=attacker 的请求(可以是 form 提交、img src、自动提交的 POST 表单),浏览器访问该页面时自动带上 Cookie,银行以为是用户本人的操作。
1
2
3
4
5
6
<!-- 攻击者只需用户访问这个页面 -->
<form action="https://bank.com/transfer" method="POST" id="hack">
<input name="amount" value="10000">
<input name="to" value="attacker_account">
</form>
<script>document.getElementById('hack').submit();</script>
2. SameSite Cookie —— 浏览器原生防护
SameSite 控制 Cookie 是否在跨站请求中被发送。Chrome 从 80 版本开始默认 SameSite=Lax。
1
2
3
4
5
6
7
8
// 服务端设置 Cookie
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'Lax' // 跨站GET导航会发送,POST/iframe不会
});
// 最严格:sameSite: 'Strict' → 任何跨站都不发送
// 但对用户体验有影响(从邮件点链接进网站需要重新登录)
三种模式的行为对比:
| 模式 | 同站请求 | 跨站GET导航 | 跨站POST/form |
|---|---|---|---|
| None | ✅ 发送 | ✅ 发送 | ✅ 发送 |
| Lax(默认) | ✅ 发送 | ✅ 发送 | ❌ 不发送 |
| Strict | ✅ 发送 | ❌ 不发送 | ❌ 不发送 |
3. CSRF Token —— 服务端验证方案
服务端生成随机 Token,嵌入页面的表单或 Cookie,提交时验证 Token 一致性。攻击者无法读取跨站页面的 Token 值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 服务端:签发和验证
const crypto = require('crypto');
function generateCSRFToken(req) {
const token = crypto.randomUUID();
req.session.csrfToken = token;
return token;
}
function validateCSRFToken(req) {
const headerToken = req.headers['x-csrf-token'];
const bodyToken = req.body._csrf;
// 比较用户提交的 Token 与 session 中存储的 Token
return (headerToken || bodyToken) === req.session.csrfToken;
}
// Express 中间件
app.use((req, res, next) => {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
if (!validateCSRFToken(req)) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
}
next();
});
前端发送 Token:
1
2
3
4
5
6
7
8
9
10
11
// 从 <meta> 标签或 Cookie 读取 Token,通过请求头发送
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ amount: 1000, to: 'friend' })
});
4. 双重提交 Cookie + Referer/Origin 校验
当服务端不能存储 Session 时(如 RESTful API),用双重提交 Cookie:Token 同时放在 Cookie 和请求头中,服务端比对两者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 签发:设置一个可以被 JS 读取的 Cookie(不能 httpOnly)
res.cookie('csrf_token', token, {
sameSite: 'Lax', // 跨站请求不会携带这个 Cookie
secure: true
});
// 验证中间件:比较 Cookie 中的 Token 和请求头中的 Token
function doubleSubmitCheck(req, res, next) {
const cookieToken = req.cookies.csrf_token;
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF check failed' });
}
next();
}
5. 综合防护策略(纵深防御)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 生产环境的全链路 CSRF 防护
function csrfProtection(req, res, next) {
// 1. SameSite Cookie(浏览器强防护)
// 2. 验证 Origin / Referer 头
const origin = req.headers.origin;
if (origin && !allowedOrigins.includes(origin)) {
return res.status(403).json({ error: 'Invalid origin' });
}
// 3. 自定义请求头(非简单请求自动触发 CORS preflight)
// 4. CSRF Token(关键操作必须验证)
// 5. 二次确认(转账等高危操作加验证码/密码)
next();
}
其实你每天都在用
- 登录任何网站:浏览器自动替你发 Cookie,SameSite=Lax 就是在这个环节保护你的
- 电信运营商的劫持广告:某些公共 WiFi 会在页面中注入 iframe,如果银行网站没有 SameSite 保护,你的 Cookie 可能被劫持
- 邮件中点击链接:
SameSite=Lax允许从邮件链到网站的 GET 请求带上 Cookie 正常登录,这正是 Lax 的「人性化」设计 - 前端 SPA 中
fetch默认不送 Cookie:fetch的credentials默认是same-origin,跨站 fetch 不送 Cookie —— 这就是为什么 RESTful API 天然有一定 CSRF 抗性 - 扫码登录后的跳转:微信扫码登录后跳转回第三方网站,这时候如果 Cookie 是
SameSite=Strict,就会登录失败,所以很多 OAuth 回调页用SameSite=None
常见误解(FAQ)
❌ 误区:只靠 SameSite Cookie 就能杜绝 CSRF
SameSite 依赖浏览器支持。2026 年 Chrome/Firefox/Safari 都已支持,但旧设备、内嵌 WebView(部分安卓)可能不支持。另外 SameSite=None + Secure 的组合在某些 iframe 场景下也是脆弱的。SameSite 应该作为第一道防线,不是唯一防线。
❌ 误区:RESTful API 不需要防 CSRF
前提不成立。RESTful API 如果只用 Cookie 做身份认证(不用 Authorization Header),仍然需要 CSRF Token。只有完全用 Authorization: Bearer <jwt> 这种需要 JS 显式添加的 Header 做认证,才天然防 CSRF。
❌ 误区:POST 请求才需要防 CSRF,GET 不需要
GET 请求虽然不应该改变数据(RESTful 规范),但旧系统仍然有用 GET 做删除/操作的 API。而且 img 标签的 src 可以发 GET 请求带 Cookie,是天然的 CSRF 向量。
❌ 误区:Referer 头检查是可靠的
Referer 可以被用户或浏览器插件禁用(Referrer-Policy: no-referrer),也可能被中间代理剥离。它应该作为辅助手段,不能作为唯一的 CSRF 防御。
一句话总结
CSRF 的本质是「浏览器替你干了坏事」——防御就是让浏览器区分「用户想干的」和「攻击者诱导的」,SameSite + Token + Origin 三道防线叠加是最佳实践。