Web 漏洞实战全解析:CSRF 攻击原理、Token 防御机制与实验验证(上)
前言:为什么必须认真研究 CSRF?
在 Web 安全体系中,CSRF(Cross-Site Request Forgery,跨站请求伪造)往往被低估。
很多开发者认为:
"只要不出现 SQL 注入和 XSS,网站就是安全的。"
但在真实生产环境中,CSRF 造成的危害并不亚于任何其他漏洞。它不需要窃取用户密码,不需要控制服务器,甚至不需要用户察觉,就可以在用户已登录的状态下,悄悄完成:
-
修改账号密码
-
变更绑定邮箱
-
提交订单
-
删除数据
-
转账支付
本实验将在一个可控、简化、可复现的环境中,完整演示:
-
CSRF 为什么会发生
-
Token 是如何工作的
-
为什么"只用 Session 不够"
-
如何通过代码彻底防御 CSRF
第一章 实验背景与理论基础
1.1 Web 身份认证的基本模型
现代 Web 应用通常采用 **"无状态 HTTP + 有状态 Session"** 的身份认证模型:
-
用户提交用户名和密码
-
服务端校验成功
-
服务端创建 Session
-
返回 SessionID 给客户端(Cookie)
-
浏览器自动携带 Cookie 请求资源
这一机制带来了极大的便利,但也埋下了 CSRF 的根源。
1.2 CSRF 的核心本质
CSRF 的本质是:
浏览器自动携带凭证 + 服务端仅依赖凭证判断身份
攻击者的核心逻辑不是"破解",而是"借用"。
CSRF 攻击成立的条件
| 条件 | 说明 |
|---|---|
| 用户已登录 | 浏览器保存了合法 Cookie |
| 请求可被伪造 | 参数可预测 |
| 服务端未校验来源 | 仅依赖 Session |
| 用户触发请求 | 点击链接 / 自动提交 |
只要四个条件同时满足,CSRF 攻击即可成功。
1.3 CSRF 与 XSS 的区别
| 对比项 | CSRF | XSS |
|---|---|---|
| 是否窃取数据 | ❌ | ✅ |
| 是否控制浏览器 | ✅(借) | ✅(控) |
| 是否需要脚本 | ❌ | ✅ |
| 攻击目标 | 用户身份 | 用户本身 |
第二章 实验环境设计与说明
2.1 实验目标
本实验旨在实现:
-
构建一个存在 CSRF 风险的业务场景
-
使用 Token 机制进行防御
-
验证 Token 的一次性有效性
-
使用 Burp Suite 复现攻击与拦截
2.2 实验拓扑结构
┌────────────┐
│ 测试机 │
│ Firefox │
│ Burp Suite │
└─────▲──────┘
│ HTTP
┌─────┴──────┐
│ 靶机 │
│ PHPStudy │
│ Apache+PHP │
└────────────┘
2.3 软件环境说明
| 组件 | 版本 | 作用 |
|---|---|---|
| OS | Windows | 靶机系统 |
| PHPStudy | 最新版 | 集成环境 |
| PHP | ≥ 7.x | 后端逻辑 |
| Firefox | ESR | 测试浏览器 |
| Burp Suite | Community | 攻击复现 |
2.4 目录结构设计
C:/phpstudy/phptutorial/WWW/csrf/
│
├── login.html
├── login1.php
├── manage-defense.php
├── add-pass.php
└── password.txt
第三章 登录系统构建(实验基础)
3.1 登录页面的设计思想
登录是所有权限控制的前提。
在本实验中,登录系统承担两个职责:
-
验证用户身份
-
初始化 Session 状态
3.2 登录前端页面(login.html)
<html>
<meta charset="utf-8">
<form action="login1.php" method="POST">
username:<br>
<input type="text" name="username"><br>
password:<br>
<input type="text" name="password"><br>
<input type="submit" value="Submit">
</form>
</html>
📌 设计说明:
-
使用 POST,避免密码出现在 URL
-
无前端校验,降低干扰
-
表单结构清晰,便于抓包分析
3.3 登录后端逻辑(login1.php)
<?php
session_start();
$usr = $_POST['username'];
$pwd = $_POST['password'];
if ($usr === 'admin' && $pwd === 'admin') {
$_SESSION["admin"] = 1;
echo "登录成功";
} else {
echo "登录失败";
}
?>
核心安全点
-
session_start()必须放在第一行 -
Session 是后续所有防御的基础
-
登录态只写入 Session,不依赖 Cookie 自定义
3.4 登录验证结果
访问:
http://<靶机IP>/csrf/login.html
输入:
admin / admin
返回:
登录成功
说明:
-
Session 已创建
-
用户处于"已认证状态"
-
浏览器自动维护会话
第四章 管理功能与 CSRF 风险点
4.1 管理页面存在的隐患
在没有任何 CSRF 防御的情况下,一个"修改密码"的功能天然具备风险:
-
请求参数可预测
-
无需二次确认
-
浏览器自动携带 Cookie
攻击者只需诱导管理员访问:
http://靶机/csrf/add-pass.php?password_new=123456&password_conf=123456
即可完成攻击。
4.2 为什么不能只靠 Session?
很多初学者误以为:
"我已经用了 Session,所以不会 CSRF。"
这是一个严重误区。
✅ Session 解决的是 你是谁
❌ Session 解决不了 请求是不是你主动发的
第五章 Token 防御机制设计(核心章节)
5.1 Token 的设计原则
| 原则 | 说明 |
|---|---|
| 不可预测 | 随机数 + 时间戳 |
| 服务端保存 | 存入 Session |
| 单次有效 | 使用即销毁 |
| 表单绑定 | 隐藏字段提交 |
5.2 管理页面(manage-defense.php)
<?php
session_start();
if ($_SESSION["admin"] != 1) {
die("Not admin");
}
$csrf_token = sha1(mt_rand() . time() . "Impossible");
$_SESSION["csrf_token"] = $csrf_token;
?>
📌 说明:
-
mt_rand():提高随机性 -
time():防止重放 -
sha1():统一长度
5.3 表单中嵌入 Token
<input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>">
浏览器会自动提交该字段,用户无感知。
5.4 密码修改与 Token 校验(add-pass.php)
<?php
session_start();
if ($_SESSION["admin"] != 1) {
die("Not admin");
}
$token = $_GET['csrf_token'];
if (!isset($_SESSION["csrf_token"]) || $_SESSION["csrf_token"] !== $token) {
die("CSRF attack detected!");
}
✅ 关键点:
-
先判断是否存在
-
再判断是否相等
-
不等立即终止流程
5.5 Token 的一次性销毁
unset($_SESSION["csrf_token"]);
这一步决定了:
-
同一个 Token 只能用一次
-
攻击者无法复用旧请求
第六章 CSRF 防御效果的实验验证
6.1 实验验证的总体思路
在完成了登录系统、管理页面以及 Token 校验机制的实现之后,必须回答一个核心问题:
这套 Token 防御方案,真的能挡住 CSRF 攻击吗?
为了科学、严谨地验证这一点,本实验设计了三类测试用例:
| 测试编号 | 测试目标 | 预期结果 |
|---|---|---|
| T1 | 正常用户修改密码 | 成功 |
| T2 | 重复提交同一 Token | 失败 |
| T3 | 删除或篡改 Token | 失败 |
只有在 T1 成功,T2/T3 全部失败 的前提下,才能证明防御机制有效。
6.2 正常业务流程验证(基准测试)
6.2.1 操作流程
-
启动 PHPStudy
-
打开 Firefox
-
访问
http://靶机IP/csrf/login.html -
使用
admin/admin登录 -
访问
manage-defense.php -
输入新密码并确认提交
6.2.2 实际结果
-
页面返回:
pwd changed ok -
password.txt文件中成功写入新密码 -
Session 中 Token 被销毁
✅ 说明:正常业务未受影响
6.3 基于 Burp Suite 的攻击复现准备
6.3.1 Burp Suite 工作原理简述
Burp Suite 是一款用于 Web 安全测试的拦截型代理工具,其核心能力包括:
-
拦截 HTTP 请求
-
修改请求参数
-
重放历史请求
-
自动化测试
在 CSRF 测试中,它用于模拟攻击者控制请求的场景。
6.3.2 浏览器代理配置
在 Firefox 中设置:
HTTP Proxy: 127.0.0.1
Port: 8080
并关闭 HTTPS 拦截(本实验仅涉及 HTTP)。
6.4 攻击场景一:Token 复用攻击
6.4.1 攻击思路
攻击者假设:
-
已经捕获过一次合法请求
-
认为 Token 可以重复使用
-
尝试直接重放请求
6.4.2 攻击步骤
-
登录系统
-
打开
manage-defense.php -
修改密码并提交
-
在 Burp Suite 中拦截请求
-
复制完整 URL(含 Token)
-
使用 Repeater 模块再次发送
示例请求:
GET /csrf/add-pass.php?password_new=123456&password_conf=123456&csrf_token=abc123 HTTP/1.1
Host: 192.168.56.101
6.4.3 攻击结果
页面返回:
Don't csrf attack!!
并且:
-
密码未被修改
-
password.txt无变化
✅ Token 复用攻击被成功拦截
6.5 攻击场景二:Token 缺失攻击
6.5.1 攻击方式
攻击者直接构造请求,不包含 Token 参数:
GET /csrf/add-pass.php?password_new=123456&password_conf=123456 HTTP/1.1
6.5.2 结果分析
服务端逻辑:
if (!isset($_SESSION["csrf_token"])) {
die("CSRF attack detected!");
}
由于 Token 已被销毁或未传入,请求被立即终止。
✅ 缺失 Token 的请求全部失败
6.6 攻击场景三:Token 篡改攻击
6.6.1 攻击方式
攻击者尝试猜测或伪造 Token:
GET /csrf/add-pass.php?password_new=123456&password_conf=123456&csrf_token=fake_token HTTP/1.1
6.6.2 结果分析
服务端校验:
if ($_SESSION["csrf_token"] !== $token) {
die("CSRF attack detected!");
}
字符串比对失败,流程终止。
✅ Token 不可预测性得到验证
6.7 多浏览器环境验证
为进一步增强实验说服力,在以下环境中重复测试:
| 浏览器 | 结果 |
|---|---|
| Firefox | ✅ |
| Chrome | ✅ |
| Edge | ✅ |
说明 Token 校验与浏览器无关,只与 Session 状态有关。
第七章 Token 防御机制的深度分析
7.1 为什么 Token 能防 CSRF?
7.1.1 攻击者做不到的事情
| 能力 | 是否存在 |
|---|---|
| 读取目标站点 Session | ❌ |
| 读取 Token 值 | ❌ |
| 预测 Token | ❌ |
| 复用 Token | ❌ |
CSRF 的本质是"借身份",而 Token 让"借不到"。
7.2 Token 与 Session 的关系
Token 并不是替代 Session,而是增强 Session。
| 层级 | 作用 |
|---|---|
| Cookie | 维持会话 |
| Session | 记录登录态 |
| Token | 验证请求合法性 |
三者缺一不可。
7.3 一次性 Token 的重要性
如果 Token 可以被多次使用,那么:
-
攻击者可以保存请求
-
随时重放
-
防御形同虚设
因此:
unset($_SESSION["csrf_token"]);
是整个防御体系的最后一道保险。
7.4 Token 生成算法的合理性分析
sha1(mt_rand() . time() . "Impossible")
| 组成部分 | 作用 |
|---|---|
| mt_rand() | 提高随机性 |
| time() | 防止回放 |
| 静态字符串 | 增加破解成本 |
| sha1() | 固定长度 |
虽然 SHA-1 已不再适合加密用途,但在 CSRF 场景中足够安全。
第八章 实验中发现的问题与改进建议
8.1 当前实现的局限性
| 问题 | 说明 |
|---|---|
| 使用 GET 提交密码 | 不安全 |
| Token 未绑定用户 | 可被横向复用 |
| 无 Token 过期时间 | 长期有效 |
| 无日志审计 | 难以溯源 |
8.2 改进方案(进阶)
8.2.1 使用 POST 提交
<form method="POST">
8.2.2 Token 绑定用户 ID
$_SESSION["csrf_token"][$uid] = $token;
8.2.3 设置 Token 有效期
$_SESSION["csrf_time"] = time();
8.2.4 增加操作日志
file_put_contents("log.txt", date("Y-m-d H:i:s") . " change pwd\n", FILE_APPEND);
第九章 实验结论(阶段性)
通过本阶段实验,可以得出以下结论:
-
仅靠 Session 无法防御 CSRF
-
Token 能有效区分请求来源
-
一次性 Token 是关键
-
Burp Suite 可完整复现攻击与防御过程
-
CSRF 与现代 Web 架构的关系
-
REST API 中的 CSRF 问题
-
SameSite Cookie 的作用与局限
-
企业级 CSRF 防御体系设计