CSRF(跨站请求伪造)详解
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户已登录身份,在用户不知情下执行非本意操作的攻击方式。常被戏称为"沉睡的间谍"或"骑在用户会话上的攻击"。
一、攻击原理(三个关键要素)
CSRF攻击能够成功,需要同时满足以下三个条件:
- 用户已登录目标网站:用户浏览器中保存了目标网站的有效认证凭证(如Cookie、Session ID)。
- 目标网站未校验请求来源:服务器仅依赖Cookie判断身份,不验证请求是否由用户本人真实发起。
- 攻击者能构造恶意请求:攻击者可以诱导用户访问一个恶意页面或点击恶意链接,该页面会自动向目标网站发起请求。
🔥 经典攻击流程示例
假设某银行的转账接口是:
POST /transfer
参数:toAccount=目标账户&amount=金额
正常流程:用户在银行网站填写表单 → 点击"转账" → 浏览器带上用户的Cookie发送请求。
CSRF攻击流程:
- 用户登录银行网站(获得合法Cookie)。
- 攻击者构造一个恶意页面,包含一个隐藏表单或图片链接:
<!-- 恶意网站 evil.com 上的代码 -->
<img src="https://bank.com/transfer?toAccount=attacker&amount=10000" style="display:none">
- 用户访问恶意网站(可能是通过钓鱼邮件、垃圾广告等诱导)。
- 浏览器自动加载图片 → 自动携带用户在
bank.com的Cookie → 发送转账请求。 - 银行服务器校验Cookie通过 → 误以为是用户本人操作 → 执行转账。
二、CSRF攻击的主要体现形式
| 攻击方式 | 原理 | 典型场景 |
|---|---|---|
| GET型 | 恶意链接或图片自动触发GET请求 | <img src="https://target.com/delete?id=123"> |
| POST型 | 自动提交隐藏表单或Ajax请求 | <form action="https://target.com/changeEmail" method="POST"> 自动提交 |
| JSON型 | 利用JavaScript发送JSON格式的API请求 | 通过fetch或XMLHttpRequest自动发送 |
实际案例
- 修改密码:用户访问恶意页面,自动发送修改密码请求,把密码改成攻击者指定的。
- 发表言论:在用户不知情下,自动发送带恶意链接的微博/论坛帖子。
- 购物下单:自动把商品加入购物车并下单,寄送到攻击者地址。
三、CSRF vs XSS(关键区别)
| 维度 | CSRF | XSS |
|---|---|---|
| 本质 | 利用身份认证机制的漏洞 | 利用用户输入输出的漏洞 |
| 是否需要用户执行代码 | 需要用户点击恶意链接/访问恶意网站 | 目标网站自己执行了恶意代码 |
| 与目标网站关系 | 利用目标网站对请求来源的信任 | 利用目标网站对用户输入的信任 |
| 是否需要注入脚本 | 不需要,攻击代码在第三方网站 | 需要,代码注入到目标网站 |
| 防范难度 | 相对容易(参考下文) | 较难(需要全面过滤) |
一句话总结:
- XSS:攻击代码在目标网站上运行,窃取用户信息或控制页面。
- CSRF:攻击代码在第三方网站上运行,利用用户已登录的身份执行操作。
四、防御CSRF的核心方案(实战建议)
✅ 1. 使用CSRF Token(最经典有效)
服务器生成一个随机的、不可预测的Token,嵌入到表单或请求头中,服务器验证必须携带正确的Token。
<!-- 服务器返回的页面包含Token -->
<form action="/transfer" method="POST"><input type="hidden" name="csrf_token" value="random_token_abc123">转账金额: <input type="text" name="amount">
</form>
工作流程:
- 用户请求表单页面 → 服务器生成Token存入Session并返回给表单。
- 用户提交表单 → 必须携带该Token。
- 服务器验证Token是否匹配 → 不匹配则拒绝请求。
优点:攻击者无法获取该Token(因为同源策略限制,恶意网站无法读取目标网站的页面源码)。
缺点:需要所有修改状态的操作都嵌入Token,实现成本稍高。
✅ 2. SameSite Cookie属性(现代浏览器推荐)
设置Cookie的SameSite属性,限制第三方域名发起的请求携带Cookie。
Set-Cookie: session_id=xyz123; SameSite=Lax; Secure; HttpOnly
SameSite取值:
Strict:任何跨站请求都不带Cookie(最安全,但可能影响正常的外链跳转)。Lax:顶级导航(如点击链接)带Cookie,但POST表单、iframe、img等不带(推荐)。None:跨站请求也带Cookie,但必须同时设置Secure(仅HTTPS)。
优点:无需修改后端代码,配置简单,兼容主流现代浏览器(除IE外)。
缺点:旧浏览器不支持(如IE 11)。
✅ 3. 验证Referer/Origin头
检查请求头中的Referer或Origin是否与当前站点域一致。
def is_valid_request(request):referer = request.headers.get('Referer')if not referer:return Falsereturn referer.startswith('https://yourdomain.com')
优点:实现简单,无需额外存储。
缺点:某些场景下Referer可能缺失(如HTTPS到HTTP、用户隐私设置),部分低版本浏览器可伪造。
✅ 4. 二次验证(高安全场景)
对于敏感操作(修改密码、转账、手机解绑等),要求用户重新输入密码/短信验证码/图形验证码。
举例:网银转账超过一定金额,必须输入短信验证码。
优点:彻底防御CSRF,即使Token泄露也无法绕过。
缺点:影响用户体验,仅适用于高敏感操作。
✅ 5. 使用自定义请求头(针对API)
要求所有API请求必须携带一个自定义Header(如X-Requested-With: XMLHttpRequest),因为跨域请求无法设置自定义Header(除非CORS明确允许)。
// 前端Ajax请求自动添加
fetch('/transfer', {headers: {'X-Requested-With': 'XMLHttpRequest'}
})
注意:这要求所有请求都是Ajax方式,传统表单提交不适用。
五、总结与最佳实践
推荐防御组合(分层防御,纵深保护)
- 基础层:使用CSRF Token + SameSite=Lax Cookie(现代框架如Django、Spring、Rails均内置支持)。
- 增强层:敏感操作加入二次验证(密码/验证码)。
- 兜底层:验证Referer/Origin作为辅助检测。
常见误区
- ❌ 仅依赖
POST请求就认为安全(攻击者同样可以伪造POST表单)。 - ❌ 使用
验证码防御所有请求(体验差,不适合每个操作)。 - ❌ 认为
SameSite能100%防御(旧浏览器不支持,且仅限制Cookie携带,无法防御已窃取到Token的场景)。
框架内置支持
- Django:
CsrfViewMiddleware(默认开启) - Spring Security:
CsrfFilter(默认开启) - Express (Node.js):
csurf中间件 - Laravel (PHP):
VerifyCsrfToken中间件
如果你需要某段具体的防御代码示例(比如用Python Flask或Java Spring实现CSRF Token),可以告诉我,我为你写一个可以直接运行的demo。
