前言

Insecure CAPTCHA(不安全的验证码),其实是在验证流程出现了逻辑漏洞。
CAPTCHA 全称为 Completely Automated Public Turing Test to Tell Computers and Humans Apart,中文名字是全自动区分计算机和人类的图灵测试。

CAPTCHA 原理

用户首先访问网页,触发页面的验证码的js模块,向谷歌服务器发起请求,谷歌服务器将验证码发给用户。
用户输入验证码发送数据回去,这里发给的是访问网站的服务器,网站的服务器拿到验证码后,再去访问谷歌的服务器,谷歌的服务器会判断验证码是否正确,再将结果返回给网站服务器。

Low

源码分析

对源码分析我们知道这段代码分为两个阶段

  1. 对用户的身份进行验证,step1,验证成功后才能进行密码修改。

  2. step2,两次输入的密码一致,则可以进行修改。

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

//第一阶段,验证身份,验证阶段为step
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
//隐藏验证码表单
$hide_form = true;

// Get input
//得到用户的新密码及确认新密码
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
//使用第三方进行身份验证
//recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
参数$privkey是服务器申请的private key,$remoteip是用户的ip,$challenge是recaptcha_challenge_field字段的值,来自前端页面 ,$response是recaptcha_response_field字段的值。函数返回ReCaptchaResponse class的实例,ReCaptchaResponse类有2个属性 :
$is_valid是布尔型的,表示校验是否有效,
$error是返回的错误代码。
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key'],
$_POST['g-recaptcha-response']
);

// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
//验证失败时
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
//验证通过时,匹配两次密码是否一致
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<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}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}

//第二阶段,检查两次密码是否一致,并更新密码
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check to see if both password 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 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 end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

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

?>

漏洞利用

  1. 直接输入两次一致的密码,使用 Burpsuite 抓包
  1. step=1 修改为 step=2 进行绕过
  1. 然后我们的密码被修改为admin123

Medium

源码分析

相比于 Low
增加了第一步是否通过的验证
即判断参数 passed_captcha 是否为 true

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

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);

// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
// 对参数passed_captcha进行验证,如果通过身份验证,该参数就为true
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<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}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}

// Check to see if both password 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 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 end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

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

?>

漏洞利用

  1. 直接输入两次一致的密码,使用 Burpsuite 抓包
  1. step=1 修改为 step=2 还有添加 &passed_captcha=true 进行绕过
  1. 然后我们的密码被修改为12345678

High

源码分析

通过对 High 级别代码的分析
该程序已经将验证流程合并
通过连续的判断将两个步骤相同的部分合并
避免第一步验证的直接改参绕过
High 级别源码的验证逻辑是当 谷歌返回的验证结果false
或者参数 recaptcha_response_field 不为 hidd3n_valu3 并且 User-Agent 参数不为 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
<?php

if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
//(通过身份验证条件)或者 (参数g-recaptcha-respon为hidd3n_valu3并且参数 HTTP_USER_AGE为 reCAPTC)就算是验证通过了
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$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
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$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 user
echo "<pre>Password Changed.</pre>";

} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}

} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}

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

// Generate Anti-CSRF token
generateSessionToken();

?>

漏洞利用

  • 因为 CAPTCHA 返回的结果我们无法控制
  1. 输入我们需要修改的密码并使用 Burpsuite 抓包
  1. 修改 User-AgentreCAPTCHA 并在 Post 包中添加 &g-recaptcha-response=hidd3n_valu3
  1. 然后我们的密码被修改为87654321

存在问题

打开 Low 级别的题目
我们发现页面居然报错了

我们需要去申请一个CAPTCHA Key
此处需要一点点的网络知识

1
https://www.google.com/recaptcha/admin/create

然后我们需要去修改 DVWA 配置文件

1
C:\phpstudy_pro\WWW\dvwa\config\config.inc.php

像我一样,懒得去申请CAPTCHA Key
可以直接借用大佬们的公私密钥

1
2
$_DVWA[ 'recaptcha_public_key' ]  = '6LeGsK8UAAAAAE3vT3pAytiffWq7Gcz_pwgIBp9r';
$_DVWA[ 'recaptcha_private_key' ] = '6LeGsK8UAAAAAFP6ZrlXp9jsvpxT9yaSrJ0-AutK';

然后我们重新登陆DVWA就会发现能正常加载

参考 & 引用

https://www.cnblogs.com/chadlas/articles/15722429.html
https://www.cnblogs.com/wybsignal/articles/17043581.html