什么是 CSRF?跨站请求伪造原理解析与防御
深入剖析 Web 安全经典漏洞 CSRF(跨站请求伪造):理解浏览器自动携带 Cookie 的信任漏洞、CSRF Token、SameSite 属性及现代防御方案。
CSRF 是 Cross-Site Request Forgery(跨站请求伪造)的缩写。虽然名字听起来颇有学术色彩,但其核心攻击逻辑却非常“接地气”:利用你已经登录的身份,诱骗你的浏览器背着你向目标网站发送恶意请求。
1. 10 岁小孩也能听懂的通俗解释(ELI5)#
想象你刚走进一家高档 VIP 会员俱乐部(例如你的网上银行或社交媒体主页):
- 你在前台成功验证身份后,工作人员在你的手背上盖了一个“VIP 认证印章”(这就是浏览器中保存的 Session Cookie)。
- 只要这个印章还在,你在俱乐部里点餐或消费时,服务员看一眼你的手背就知道你是尊贵的会员,不再需要你重复出示身份证。
- 离开俱乐部后,你路过了一个来路不明的路边摊(恶意网站
evil.com)。 - 摊主偷偷写了一张转账单:“从该会员的账户转账 1 万元给我”,然后趁你不注意抓起你盖有 VIP 印章的手在纸上按了个手印,并派人送去了 VIP 俱乐部。
- 俱乐部的前台看到印章真实有效 误以为真的是你本人的授权操作 瞬间完成了转账扣款!
你自始至终都没有在银行网站上点击过转账,但你的浏览器却被当作“工具人”代劳了。这就是 CSRF。
flowchart LR
subgraph AttackerSite["恶意网站 (evil.com)"]
MaliciousScript["隐藏脚本 / 自动提交表单<br/>POST https://bank.com/transfer"]
end
subgraph UserBrowser["用户浏览器 (Client)"]
Victim["已登录的用户<br/>(持有有效的 Session Cookie)"]
end
subgraph BankServer["银行目标服务器 (bank.com)"]
Backend["Cookie 校验通过<br/>-> 错误执行恶意转账!"]
end
Victim -->|1. 访问恶意网页| MaliciousScript
MaliciousScript -->|2. 静默触发跨站请求| Victim
Victim -->|3. 自动附加 Cookie 并发出请求| Backend
2. CSRF 漏洞成立的两个关键前提#
CSRF 的发生既不需要黑客破解你的密码,也不需要盗取你的 Session 密钥,它完全建立在浏览器与服务器之间的盲目信任之上:
- 浏览器的 Cookie 自动携带机制(Ambient Credentials): 浏览器向目标域名(如
bank.com)发送请求时,会默认自动附带该域名下存储的所有 Cookie,而不管这个请求是在哪个页面发起的。 - 服务器缺乏请求来源校验: 服务器只机械地校验“请求是否带有合法 Cookie(是否已登录)”,却忽略了最致命的问题:“这个请求到底是不是从我自家的前端页面发出来的?”。
3. 彻底厘清:CSRF 与 XSS 的本质区别#
很多开发者经常混淆 XSS 和 CSRF,两者的核心差异在于:
| 比较维度 | XSS (跨站脚本攻击) | CSRF (跨站请求伪造) |
|---|---|---|
| 攻击本质 | 在目标网站内注入并执行恶意 JavaScript 脚本 | 诱导浏览器从外部网站向目标网站发送未授权请求 |
| 是否盗取 Cookie | 可以直接读取并盗走 Cookie(除非配置了 HttpOnly) | 无法读取 Cookie(只是借用浏览器的身份凭证) |
| 产生根源 | 前端没有对用户输入进行严格过滤或转义渲染 | 依赖了浏览器跨站自动携带 Cookie 的底层机制 |
[!NOTE] XSS 相当于小偷直接偷走了你的身份证;而 CSRF 则是骗子拉着你拿着身份证去办事,你甚至不知道自己办了什么事。
4. 常见攻击场景复现#
场景一:利用 <img> 标签触发未防护的 GET 请求#
如果敏感的业务操作不规范地使用了 HTTP GET 方法:
<!-- 浏览器自动静默拉取图片,并自动附带受害者在银行的会话 Cookie -->
<img src="https://bank.com/api/transfer?to=attacker&amount=10000" width="0" height="0" />html场景二:利用隐藏表单自动提交 POST 请求#
即使你的接口严格使用了 HTTP POST,黑客依然可以在恶意页面中构造隐藏表单,并在页面加载时通过 JavaScript 自动提交:
<form id="csrfForm" action="https://social.com/api/delete-account" method="POST">
<input type="hidden" name="confirm" value="true" />
</form>
<script>
// 页面加载完成后立即自动提交
document.getElementById('csrfForm').submit();
</script>html5. 全方位的 CSRF 防御策略#
1. CSRF Token(同步令牌模式)#
这是最经典也是最可靠的防御方案:
- 当服务端渲染表单或初次建立会话时,生成一个唯一的、高熵的随机字符串(CSRF Token)并绑定到用户会话中。
- 前端提交表单或发起请求时,必须在请求体或请求头中携带该 Token。
- 受限于浏览器的同源策略(SOP),第三方恶意网站无法跨域读取你网站上的 CSRF Token,因此伪造的请求会因缺少正确 Token 而被直接拒绝。
<form action="/transfer" method="POST">
<!-- 服务端注入的保密 CSRF Token -->
<input type="hidden" name="_csrf" value="d9a8f7b2c3e4..." />
<input type="number" name="amount" />
<button type="submit">确认转账</button>
</form>html2. Cookie 的 SameSite 属性(现代浏览器的原生盾牌)#
通过在 Set-Cookie 响应头中配置 SameSite 属性,可以严格控制 Cookie 在跨站请求中的发送策略:
SameSite=Strict:最为严格,完全禁止在任何跨站请求中发送 Cookie(安全性最高,但用户从外部链接点击进入时需重新登录)。SameSite=Lax(现代浏览器默认值):阻止在 POST、隐藏表单、<img>等跨站请求中发送 Cookie;仅在安全的顶级导航(如直接点击<a>链接)时允许携带。SameSite=None:在所有跨站场景下均允许发送 Cookie(必须配合Secure属性在 HTTPS 下使用)。
Set-Cookie: sessionId=xyz123; Path=/; Secure; HttpOnly; SameSite=Laxhttp[!TIP] 为身份认证 Cookie 设置
SameSite=Lax(或Strict)并配合Secure和HttpOnly,即可直接封死绝大多数基础 CSRF 攻击路径。
3. 双重 Cookie 校验(Double Submit Cookie)#
适用于无状态(Stateless)或单页应用(SPA)架构:
- 服务端设置一个前端可读的随机 Token Cookie(例如
XSRF-TOKEN)。 - 前端 JavaScript 读取该 Cookie 的值,并在发起 AJAX 请求时将其注入到自定义请求头中(例如
X-XSRF-TOKEN)。 - 后端对比请求头中的值与 Cookie 中的值,一致则认定请求合法。
4. 校验 Origin / Referer / Sec-Fetch-Site 请求头#
在后端拦截器或网关层,严格核对请求来源域名:
export function verifyOrigin(req, res, next) {
const origin = req.headers['origin'] || req.headers['referer'];
const allowedHost = 'https://mybank.com';
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
if (!origin || !origin.startsWith(allowedHost)) {
return res.status(403).json({ error: 'CSRF Protection: Invalid Request Origin' });
}
}
next();
}javascript6. SPA 与 JWT 时代下的 CSRF 现状#
在以前后端分离、单页应用(SPA)为主流的现代架构中:
- 如果将 JWT 令牌保存在内存中,并在发起请求时手动附加到
Authorization: Bearer <token>请求头中,天然对 CSRF 免疫,因为浏览器绝不会自动跨站拼接自定义认证请求头。 - 但如果为了方便或持久化将会话 JWT 存放在 Cookie 中,CSRF 威胁依然完好存在,仍必须严格部署上述防护策略。
7. 总结#
CSRF 深刻揭示了 Web 世界中“无条件信任”所带来的安全隐患。
构建坚不可摧的 Web 应用防御准则:
- 涉及数据变更的操作绝不使用
GET方法。 - 确保所有身份凭证 Cookie 均配置了
SameSite=Lax或SameSite=Strict。 - 关键业务表单与接口强制推行 CSRF Token 或自定义校验请求头。
- 后端中间件严格核验
Origin和Sec-Fetch-Site请求来源。