一、原理:
利用用户登录态伪造操作
CSRF(Cross-Site Request Forgery,跨站请求伪造)是攻击者“借刀杀人”,借用用户浏览器中已有的登录状态,诱导用户完成攻击者指定的操作。
1. 基本机制分解
1)浏览器的默认行为:自动带上 Cookie(包括 sessionid)
当你访问一个网站并登录后,例如:
POST /login
Host: bank.com
Cookie: sessionid=abc123
-
网站会通过 Cookie(如
sessionid
)记录你的登录状态。 -
登录后,每次你再访问这个网站,浏览器都会自动带上 Cookie:
GET /user/profile
Cookie: sessionid=abc123
这是 浏览器的默认行为,不管你怎么发请求,只要是访问 bank.com
,就自动带上它的 Cookie。
2)网站服务器的判断逻辑
网站一般只根据 Cookie 判断用户身份:
if request.cookie["sessionid"] is valid:allow_operation()
所以请求 只要带了合法的 Cookie,网站就以为是“你自己操作的”。
3)攻击者不能获取 Cookie,但能让浏览器代发请求
攻击者 无法直接读取你的 Cookie,但却可以通过诱导你访问恶意页面,让浏览器替他发出请求。
浏览器会在请求中 自动附带 Cookie,即使这是攻击者构造的请求!
攻击者只需要这样:
<img src="https://bank.com/transfer?to=attacker&amount=10000">
当你一打开这个网页:
-
浏览器会访问
bank.com
; -
自动附带你的 Cookie(如 sessionid);
-
请求落到银行服务器上;
-
银行服务器识别 Cookie 合法,就执行了“转账”。
结果是:你替攻击者完成了操作,自己还一无所知。
2. 类比解释:伪造你发指令
想象这个场景:
-
你在一个公司门禁系统中,登录了一个可以发指令的管理后台;
-
攻击者不能进公司,但可以给你发一个“点击这个链接”的诱导邮件;
-
你一不小心点开,就自动触发了一个“给他打钱”的命令;
-
门禁系统看到是你发的,指令合法,就执行了。
攻击者没权限,但他“骗你”用你的权限帮他做了事。
3. 条件总结(CSRF 成功的关键前提)
-
用户已登录目标网站,且登录状态仍有效(Cookie 存在);
-
目标网站通过 Cookie 识别身份(常见);
-
请求没有额外校验(如 CSRF token);
-
攻击者诱导用户访问了恶意链接或页面;
-
浏览器自动发送 Cookie,服务器误以为是用户行为。
4. 攻击流程图(逻辑顺序)
[用户登录 bank.com] → [获得 sessionid Cookie]↓
[访问恶意网站] → [构造对 bank.com 的请求]↓
[浏览器自动带上 sessionid]↓
[bank.com 收到请求] → [识别为用户操作] → 执行了操作
5. 攻击者和用户的角色区别
项目 | 用户 | 攻击者 |
---|---|---|
拥有 Cookie | 是 | 否 |
能发请求 | 是 | 能骗用户发 |
能伪造内容 | 无法伪造 Cookie,但能伪造请求内容 | |
能操作目标站 | (自己操作) | (借助用户操作) |
总结
CSRF 利用的不是系统漏洞,而是 Web 的信任机制 + 用户不知情行为 + 浏览器的自动 Cookie 行为。
二、构造:
自动提交表单
攻击者构造一个伪造的表单,提前填好表单字段,让用户在访问时自动提交,借此触发目标网站上的敏感操作(如转账、修改密码等)。
1. 构造基本结构
<form action="https://bank.com/transfer" method="POST"><input type="hidden" name="to" value="attacker123"><input type="hidden" name="amount" value="10000">
</form><script>document.forms[0].submit(); // 页面加载后自动提交表单
</script>
2. 构造流程详解
1)<form>
标签构建 POST 请求
<form action="https://bank.com/transfer" method="POST">
-
action
是目标地址; -
method="POST"
表示发起 POST 请求; -
请求将带上表单中的参数。
2)使用隐藏字段构造参数
<input type="hidden" name="to" value="attacker123">
<input type="hidden" name="amount" value="10000">
-
这些字段用户看不到;
-
但它们会作为 POST 的参数发送出去;
-
模拟的是用户提交表单的过程。
3)使用 JavaScript 自动提交表单
document.forms[0].submit();
-
页面加载时立即提交;
-
用户根本无感知。
3. 构造后发出的请求(抓包效果)
POST /transfer HTTP/1.1
Host: bank.com
Content-Type: application/x-www-form-urlencoded
Cookie: sessionid=abcdef123456to=attacker123&amount=10000
浏览器自动带上用户登录状态中的 Cookie(如 sessionid
),服务器误以为是用户自己发出的请求。
4. 实战演示用法(真实攻击逻辑)
假设:
目标网站 bank.com
有如下逻辑:
-
POST /transfer
可以转账; -
需要参数
to
和amount
; -
只要 Cookie 中的 session 合法就执行操作;
-
没有 CSRF Token 或 Referer 校验。
攻击页面代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>你中奖了</title>
</head>
<body><h1>恭喜你中奖了!正在处理领奖信息...</h1><form id="csrfForm" action="http://localhost:8000/transfer" method="POST"><input type="hidden" name="to" value="attacker_account"><input type="hidden" name="amount" value="10000"></form><script>window.onload = function() {document.getElementById('csrfForm').submit();}</script>
</body>
</html>
攻击流程:
-
用户在登录了
bank.com
后,未退出; -
用户访问了攻击者构造的钓鱼网页;
-
浏览器在加载页面时,自动提交表单;
-
bank.com
收到合法 Cookie + 合法参数; -
成功执行“给攻击者转账”操作。
5. 关键点
特性 | 描述 |
---|---|
请求类型 | POST |
参数构造方式 | 隐藏字段 <input type="hidden"> |
自动触发方式 | JS:form.submit() |
浏览器行为 | 自动携带 Cookie |
用户感知程度 | 无感知,访问页面即中招 |
6. 限制与防御
这种方式虽然常见,但也容易被防御:
防御方式 | 是否能阻止这种攻击 |
---|---|
CSRF Token 校验 | 可以阻止 |
Referer 校验 | 可以阻止 |
SameSite Cookie 限制 | 可以阻止 |
登出机制不当 | 无法阻止 |
用户粗心点开链接 | 攻击成功 |
总结
自动提交表单是构造 CSRF 的最主流手段,它依赖浏览器自动携带 Cookie + 用户无感知行为,在攻击页面中用隐藏字段 + JS 一键触发操作。
=======================================
img/src 请求
<img>
标签中的 src
属性,只要被浏览器解析,就会自动发出 GET 请求。
攻击者可利用这一点,构造一段带有 img 标签的 HTML 页面,诱导用户访问,让用户在不知情的情况下向目标网站发起请求。
最关键的是:浏览器在发出这个 GET 请求时,会自动携带当前域名下的 Cookie(如 sessionid
、token
等登录态信息)。
如果目标网站没有做好 CSRF 防御,攻击就可能成功。
1. 构造示例
<!-- 攻击者网页上的 HTML 代码 -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">
2. 浏览器行为分析
-
用户访问攻击者构造的网页;
-
<img>
标签被浏览器解析; -
浏览器自动向
https://bank.com/transfer?...
发起 GET 请求; -
自动带上当前用户的 Cookie(如
sessionid=abcdef123456
); -
如果目标接口没有验证 Referer、Token,服务器会误以为是合法操作;
-
攻击成功。
发出的请求大致
GET /transfer?to=attacker&amount=10000 HTTP/1.1
Host: bank.com
Cookie: sessionid=abcdef123456
User-Agent: Mozilla/5.0 ...
3. 攻击条件
要让攻击成功,必须满足以下条件:
条件 | 说明 |
---|---|
用户已登录目标网站 | 否则没有有效 Cookie,无法伪造操作 |
Cookie 中记录了身份凭证 | 如 sessionid 或 JWT |
目标接口使用 GET 实现敏感操作 | 如转账、绑定邮箱、删除内容等(极其不安全) |
没有 CSRF Token 或 Referer 检查 | 否则请求会被拦截 |
4. 其它类似的标签(也能触发 GET)
攻击者可以不止使用 <img>
,还可以使用以下方式:
标签 | 示例代码 |
---|---|
<img> | <img src="..."> |
<script> | <script src="..."> |
<iframe> | <iframe src="..."> |
<link> | <link rel="stylesheet" href="..."> |
<object> | <object data="..."> |
5. img/src 的限制
限制点 | 说明 |
---|---|
只能发 GET 请求 | 无法发 POST 请求 |
无法自定义请求头 | 不能设置 Referer、User-Agent 等 |
无法处理响应 | JavaScript 无法获取返回内容 |
一旦目标接口有验证就失败 | CSRF Token、SameSite Cookie、Referer 限制 |
6. 使用场景
虽然 POST 请求一般更危险,但一些老旧或不规范的网站,可能把敏感操作写成 GET 请求,比如:
-
GET
/deleteAccount?id=123
-
GET
/addFriend?user=evil
-
GET
/transfer?to=attacker&amount=5000
这类系统就特别容易被 <img>
标签攻击。
7. 攻击场景举例
1)删除某用户帖子:
<img src="https://forum.com/deletePost?id=999">
用户点击钓鱼链接或访问攻击页面后,该帖子被删除。
2)给攻击者点赞/加好友:
<img src="https://social.com/addFriend?uid=attacker">
用户以为自己在看图片,其实是在无意间帮攻击者加了好友。
8. 安全建议(从服务端角度)
防御措施 | 有效性 |
---|---|
禁止用 GET 做敏感操作 | 非常重要 |
使用 CSRF Token | 推荐 |
检查 Referer / Origin | 可选 |
设置 SameSite Cookie | 可阻止跨站请求 |
用户操作需二次确认 | 提高安全性 |
总结
img/src 是最“隐蔽”的 CSRF 构造方式之一,用户根本意识不到任何操作,但背后其实可能在给别人转账、点赞、删除内容,属于“无交互感知攻击”,而浏览器的默认行为(自动发 GET、自动带 Cookie)正是它得逞的根源。
三、 防御:
Token 验证
**CSRF Token(跨站请求伪造令牌)**是一段由服务器生成、随机、不可预测的字符串,用来绑定用户请求和用户身份之间的唯一性,防止恶意网站伪造用户的请求。
它不保存在 Cookie 中,而是嵌入在 HTML 页面中,并随着每个请求一起发送回来。
为什么需要 Token?
因为 Cookie 是自动发送的,攻击者可以构造伪造请求利用用户的登录态。而 Token 是攻击者无法提前获取、又必须提交的验证参数。
所以只要验证这个 Token,服务器就能判断请求是不是由真实用户页面发起的。
1. 工作原理详解
1)用户访问页面
-
服务器生成一个随机 Token:
session['csrf_token'] = 'Xyz123Abc...'
-
把这个 Token 插入到 HTML 页面里(常见方式是 form 隐藏字段):
<input type="hidden" name="csrf_token" value="Xyz123Abc..." />
2)用户提交表单时
浏览器会将该隐藏字段和其他字段一起发送:
POST /transfer HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: sessionid=abc123to=tom&amount=1000&csrf_token=Xyz123Abc...
3)后端校验
后端取出提交的 Token 和保存在会话(session)中的 Token 进行比对:
if request.form['csrf_token'] != session['csrf_token']:return 'CSRF ATTACK DETECTED'
一致:允许操作
不一致/缺失:拦截攻击
2. 为什么能防御 CSRF?
因为:
-
攻击者不能获取用户的 Token(它是嵌入 HTML 的,不能跨域读取);
-
攻击者无法猜出 Token 内容(足够随机);
-
即使攻击者构造了相同参数请求,缺了 Token 也无法通过验证。
3. 示例(完整 Flask 演示)
from flask import Flask, request, session, render_template_string, redirect
import secretsapp = Flask(__name__)
app.secret_key = 'your-secret-key'@app.route('/')
def form():# 生成随机 CSRF Tokentoken = secrets.token_hex(16)session['csrf_token'] = tokenreturn render_template_string('''<form action="/submit" method="post"><input type="text" name="to" placeholder="转账对象"><input type="text" name="amount" placeholder="金额"><input type="hidden" name="csrf_token" value="{{token}}"><input type="submit" value="提交"></form>''', token=token)@app.route('/submit', methods=['POST'])
def submit():if session.get('csrf_token') != request.form.get('csrf_token'):return "CSRF ATTACK BLOCKED"return f"已向 {request.form['to']} 转账 {request.form['amount']} 元"if __name__ == '__main__':app.run(debug=True)
4. 建议
方法 | 描述 |
---|---|
每个表单独立 Token | 提高安全性(避免 Token 重用) |
Token 过期时间 | 防止长期有效的令牌被利用 |
双 Token 机制 | Cookie + 表单同时携带,提升安全性 |
Ajax 请求 | 放在自定义 Header 中,防止被 HTML form 模拟提交 |
注意:GET 请求无法用 Token 防御!
因为 <img>
、<a>
、<script>
标签都可以发 GET 请求,但无法携带 Token,所以:
-
敏感操作必须使用 POST/PUT/DELETE 请求
-
且要配合 Token 验证
总结
特性 | 描述 |
---|---|
核心机制 | 最强大的 CSRF 防御方式 |
独立于浏览器行为 | 不依赖 Cookie 属性 |
不影响用户体验 | 用户无感知 |
实现略复杂 | 需要页面与服务端同步 Token |
=======================================
Referer 检查
Referer(原意是 Referrer,拼写错了但沿用了)是 HTTP 请求头的一部分,它记录了:
这个请求是从哪个页面跳转过来的。
例如,在网页中点击了一个链接或提交了一个表单,请求头就会带上:
Referer: https://www.bank.com/account
这个字段就告诉服务器:用户当前的请求是从 https://www.bank.com/account
页面发出来的。
1. Referer 检查的原理
核心逻辑:只允许来自本站页面发起的请求。
如果服务端发现请求的 Referer 不是自己的域名,则判定请求不是合法来源,有可能是 CSRF 攻击。
2. Referer 检查怎么实现?
以 Python Flask 为例:
from flask import request@app.route('/transfer', methods=['POST'])
def transfer():referer = request.headers.get('Referer')if referer is None or not referer.startswith("https://www.bank.com"):return "非法请求,可能是 CSRF 攻击", 403# 执行转账操作return "转账成功"
3. 攻击与防御场景对比
攻击请求(来自攻击者网站):
攻击者构造一个页面,诱导你点击按钮:
<form action="https://www.bank.com/transfer" method="POST"><input type="hidden" name="amount" value="10000"><input type="hidden" name="to" value="attacker">
</form>
<script>document.forms[0].submit()</script>
点击后浏览器发出 POST 请求:
POST /transfer HTTP/1.1
Host: www.bank.com
Referer: https://evil.com/attack.html
Cookie: session=123456
Referer 检查发现不是本域名,拦截。
正常请求(正常用户操作):
用户在 https://www.bank.com/account
页面点击“转账”,发起请求:
Referer: https://www.bank.com/account
服务器一看,是本站页面发来的,请求放行。
4. Referer 检查的优缺点
优点:
优点 | 描述 |
---|---|
简单 | 不需要在页面嵌入 Token,服务器代码也很简单 |
快速生效 | 服务端只需增加 Referer 检查逻辑即可 |
对用户透明 | 不影响用户体验,不需要页面修改 |
缺点:
缺点 | 描述 |
---|---|
Referer 可能为空 | 某些浏览器或隐私插件会去掉 Referer,导致误判 |
有些合法请求可能是跨域 | 比如 OAuth 登录回调、第三方支付跳转,Referer 不是本站但合法 |
攻击者不能伪造 Referer,但可以构造没有 Referer 的请求 | 比如通过 <img> 、<iframe> 发起请求,浏览器可能不带 Referer |
不适用于 API 接口 | 移动端请求或者 AJAX 跨域接口可能没有 Referer,造成误判或不适用 |
5. 实际使用建议
Referer 检查作为 辅助防御机制 是很有价值的,但不能作为唯一防线,因为:
-
它容易被绕过(Referer 空值、浏览器兼容性等);
-
有时候会拦正常请求;
-
安全级别不如 CSRF Token 或 SameSite Cookie。
6. 最佳实践
场景 | 建议 |
---|---|
Web 表单提交 | 推荐使用 CSRF Token + Referer 检查双重防御 |
AJAX 请求 | 建议加 CSRF Token 头部或参数验证 |
开放接口(如 API) | 不建议使用 Referer,推荐使用 OAuth / 签名机制等 |
总结
Referer 检查是一种基于请求来源的 CSRF 防御手段,通过验证请求的 Referer
是否来自本站,拦截非本站发起的请求。虽然简单有效,但存在兼容性和安全边界问题,建议作为 CSRF Token 的补充手段。
=======================================
SameSite Cookie
SameSite
是 Cookie 的一个属性,用来控制浏览器在“跨站请求”时是否发送这个 Cookie。
它是由浏览器实现的安全策略,不依赖后端逻辑。主要用来防御跨站攻击(特别是 CSRF)。
跨站请求是什么意思?
举例说明:
-
你在浏览
https://evil.com
,这上面有一个表单偷偷向https://bank.com/transfer
发 POST 请求。 -
浏览器默认会携带你在
bank.com
登录时留下的 Cookie(含 session id)! -
bank.com
后台认为你是登录用户,直接处理请求了!这就是 CSRF!
SameSite 属性有哪几种值?
SameSite 值 | 描述 | 是否允许跨站请求带 Cookie |
---|---|---|
Strict | 最严格,完全禁止 | 不允许任何跨站请求带 Cookie |
Lax | 默认,兼容性好 | 允许 GET 请求带 Cookie,禁止 POST |
None | 不限制 | 始终允许,但必须配合 Secure (即 HTTPS) |
1. 各模式详细解读
1)SameSite=Strict
只允许 同站请求(用户主动点击本站链接、表单提交等),否则浏览器不会附带 Cookie。
可防御所有类型的 CSRF!
缺点:
-
用户从其他站点点击链接访问本站后,可能需要重新登录。
2)SameSite=Lax
略微放宽,允许以下情况携带 Cookie:
-
链接跳转(GET)
-
资源加载(img、iframe)
-
表单或脚本的 POST 不携带 Cookie(可防御 CSRF)
优点:
-
大多数网站默认使用这个值,兼容性好;
-
可防御大多数 CSRF(POST 被阻止)。
3)SameSite=None
这是唯一允许完全跨站请求带 Cookie的选项。
但有个前提:必须同时设置 Secure
属性,且使用 HTTPS,否则浏览器会拒绝设置 Cookie!
如果不加 Secure
,Chrome 直接不保存这个 Cookie!
2. 设置示例(Flask)
@app.route("/set_cookie")
def set_cookie():resp = make_response("设置成功")# SameSite 可以设置为 'Strict' / 'Lax' / 'None'resp.set_cookie("session_id", "123456", samesite="Lax", secure=False)return resp
如果设置 SameSite=None,必须加 secure=True
,并启用 HTTPS。
3. SameSite 的 CSRF 防御原理
浏览器发起跨站请求(如:在恶意网站中自动提交表单):
-
如果
SameSite=Strict
或Lax
,浏览器不会附带 session_id -
后端检测不到登录态,CSRF 请求失效
4. 实际建议
场景 | SameSite 推荐值 |
---|---|
普通用户站点(需防 CSRF) | Lax (推荐) |
管理后台系统(高安全性) | Strict |
第三方服务需带 Cookie | None + Secure + HTTPS |
5. 检查 Cookie 设置
打开浏览器控制台(F12 → Application → Cookies),可以看到:
-
Name
:session_id -
Value
:xxxx -
SameSite
:Strict / Lax / None -
Secure
:是否开启
总结
SameSite 是浏览器级别的防御机制,用来让“跨站请求时 Cookie 不再自动发送”,从根源上限制 CSRF 攻击。