DVWA靶场保姆级通关教程--06不安全验证机制_dvwa不安全的验证码高级
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
-
目录
文章目录
前言
原理详解
1. 前后端验证逻辑不一致
2. 验证码值保存在客户端
3. 验证码可预测或重复
4. 验证码验证与逻辑解耦
一、处理关卡报错
二、low级别源码分析
三、medium级别源码分析
三、high级别源码分析
安全点说明:
四、impossible级别源码分析
总结:Impossible级别的安全设计
前言
提示:这里可以添加本文要记录的大概内容:
Insecure CAPTCHA 模块的含义是:CAPTCHA 的验证机制存在安全漏洞,可以被攻击者轻松绕过,从而进行自动化攻击(例如暴力破解登录密码)。
原理详解
在 DVWA 的这个模块中,存在以下常见的实现漏洞:
1. 前后端验证逻辑不一致
-
前端页面展示了验证码图片(如一个 5 位数字),用户输入后提交表单。
-
但后端未正确校验或校验方式过于简单,甚至根本未检查验证码输入是否正确。
-
攻击者可以直接跳过验证码输入字段,构造 POST 请求攻击。
2. 验证码值保存在客户端
-
有的实现会把验证码值直接保存在 cookie、hidden field 或 session 中,攻击者可以通过抓包或调试 JS 获取验证码答案。
-
甚至可以通过查看源码或 response 来获取验证码值。
3. 验证码可预测或重复
-
如果验证码使用的是伪随机函数(如
rand()
)生成,而种子固定,攻击者可预测其值。 -
或者验证码值在每次访问时不更新(比如 10 分钟内都是同一个),那么攻击者只需识别一次,就可反复使用。
4. 验证码验证与逻辑解耦
-
某些实现中,验证码验证逻辑在前端处理,而没有在服务端做真正校验,攻击者可直接跳过验证逻辑。
提示:以下是本篇文章正文内容,下面案例可供参考
一、处理关卡报错
$_DVWA[ \'recaptcha_public_key\' ] = \'\';
$_DVWA[ \'recaptcha_private_key\' ] = \'\';
改为:
$_DVWA[ \'recaptcha_public_key\' ] = \'6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg\';
$_DVWA[ \'recaptcha_private_key\' ] = \'6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ\';
重启或者退出dvwa重新登录,修改成功
二、low级别源码分析
<?php// 如果提交了表单,并且 step 是 1(第一步:验证码验证)if( isset( $_POST[ \'Change\' ] ) && ( $_POST[ \'step\' ] == \'1\' ) ) { $hide_form = true; // 隐藏第一步的 CAPTCHA 表单 // 获取用户输入的新密码和确认密码 $pass_new = $_POST[ \'password_new\' ]; // 新密码 $pass_conf = $_POST[ \'password_conf\' ]; // 确认密码 // 验证 CAPTCHA,使用第三方(Google reCAPTCHA) $resp = recaptcha_check_answer( $_DVWA[ \'recaptcha_private_key\'], // 使用私钥 $_POST[\'g-recaptcha-response\'] // 用户填写的 CAPTCHA 响应 ); // 如果 CAPTCHA 验证失败 if( !$resp ) { $html .= \"
The CAPTCHA was incorrect. Please try again.\"; // 提示 CAPTCHA 错误 $hide_form = false; // 显示 CAPTCHA 表单 return; // 停止执行后续逻辑 } else { // CAPTCHA 验证通过,检查两个密码是否一致 if( $pass_new == $pass_conf ) { // 如果一致,显示第二步确认提交表单 echo \"
You passed the CAPTCHA! Click the button to confirm your changes.\"; } else { $html .= \"
Both passwords must match.\"; // 密码不一致,提示错误 $hide_form = false; // 显示 CAPTCHA 表单 } }}// 如果提交了表单,并且 step 是 2(第二步:真正修改密码)if( isset( $_POST[ \'Change\' ] ) && ( $_POST[ \'step\' ] == \'2\' ) ) { $hide_form = true; // 隐藏 CAPTCHA 表单 // 获取用户输入的新密码和确认密码 $pass_new = $_POST[ \'password_new\' ]; $pass_conf = $_POST[ \'password_conf\' ]; // 再次检查密码是否一致 if( $pass_new == $pass_conf ) { // 对密码进行 SQL 注入防护 $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 ); // 对密码进行 md5 哈希 // 构造 SQL 更新语句,更新当前用户的密码 $insert = \"UPDATE `users` SET password = \'$pass_new\' WHERE user = \'\" . dvwaCurrentUser() . \"\';\"; $result = mysqli_query($GLOBALS[\"___mysqli_ston\"], $insert ) or die( \'
\' . ((is_object($GLOBALS[\"___mysqli_ston\"])) ? mysqli_error($GLOBALS[\"___mysqli_ston\"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . \'\' ); echo \"
Password Changed.\"; // 提示密码修改成功 } else { echo \"
Passwords did not match.\"; // 提示密码不一致 $hide_form = false; // 显示表单 } ((is_null($___mysqli_res = mysqli_close($GLOBALS[\"___mysqli_ston\"]))) ? false : $___mysqli_res); // 关闭数据库连接}?>
核心安全问题(即 “Insecure CAPTCHA” 原理)
攻击者可以跳过第一个 CAPTCHA 表单,直接构造第二步请求(step=2)提交新密码,因为 第二步中没有再次验证 CAPTCHA 是否通过,也没有其他身份校验机制。
所以这里输入密码,用bp抓包,将step的值从1直接改为2,即跳过安全验证,直接改密
抓包结果如下:
在返回的数据包中发现修改成功
三、medium级别源码分析
<?phpif( isset( $_POST[ \'Change\' ] ) && ( $_POST[ \'step\' ] == \'1\' ) ) { // 如果提交了表单并且当前步骤为第1步 // Hide the CAPTCHA form $hide_form = true; // 隐藏 CAPTCHA 表单 // Get input $pass_new = $_POST[ \'password_new\' ]; // 获取新密码 $pass_conf = $_POST[ \'password_conf\' ]; // 获取确认密码 // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( // 调用 Google reCAPTCHA 接口进行验证 $_DVWA[ \'recaptcha_private_key\' ], // 使用 DVWA 中设置的私钥 $_POST[\'g-recaptcha-response\'] // 获取表单中用户填写的 CAPTCHA 响应 ); // Did the CAPTCHA fail? if( !$resp ) { // 如果 CAPTCHA 验证失败 // What happens when the CAPTCHA was entered incorrectly $html .= \"
The CAPTCHA was incorrect. Please try again.\"; // 显示错误信息 $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 \"
You passed the CAPTCHA! Click the button to confirm your changes.// CAPTCHA 验证成功提示 // 第二步的确认修改表单 // 标记为第2步 // 隐藏域保存新密码 // 隐藏域保存确认密码 // 隐藏域标记 CAPTCHA 已通过 // 提交按钮 \"; } else { // Both new passwords do not match. $html .= \"
Both passwords must match.\"; // 显示密码不一致的错误信息 $hide_form = false; // 显示表单 } }}if( isset( $_POST[ \'Change\' ] ) && ( $_POST[ \'step\' ] == \'2\' ) ) { // 如果当前为第2步 // Hide the CAPTCHA form $hide_form = true; // 隐藏 CAPTCHA 表单 // Get input $pass_new = $_POST[ \'password_new\' ]; // 获取新密码 $pass_conf = $_POST[ \'password_conf\' ]; // 获取确认密码 // Check to see if they did stage 1 if( !$_POST[ \'passed_captcha\' ] ) { // 如果未通过 CAPTCHA 验证(没有设置 passed_captcha 字段) $html .= \"
You have not passed the CAPTCHA.\"; // 提示用户未通过 CAPTCHA $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)) ? \"\" : \"\")); // 对密码进行转义防止 SQL 注入 $pass_new = md5( $pass_new ); // 将密码使用 md5 加密 // Update database $insert = \"UPDATE `users` SET password = \'$pass_new\' WHERE user = \'\" . dvwaCurrentUser() . \"\';\"; // 构造 SQL 更新语句 $result = mysqli_query($GLOBALS[\"___mysqli_ston\"], $insert ) or die( \'
\' . ((is_object($GLOBALS[\"___mysqli_ston\"])) ? mysqli_error($GLOBALS[\"___mysqli_ston\"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . \'\' ); // 执行 SQL 并在出错时输出错误信息 // Feedback for the end user echo \"
Password Changed.\"; // 提示密码修改成功 } else { // Issue with the passwords matching echo \"
Passwords did not match.\"; // 提示密码不一致 $hide_form = false; // 显示表单 } ((is_null($___mysqli_res = mysqli_close($GLOBALS[\"___mysqli_ston\"]))) ? false : $___mysqli_res); // 关闭数据库连接}?>
medium 级别与 low 级别的区别是:
-
增加了对 CAPTCHA 验证通过的状态存储:通过隐藏字段
passed_captcha
传递。 -
在第二步执行修改前,检查
passed_captcha
是否存在,以避免跳过 CAPTCHA 验证。
不过这仍然可以被伪造表单绕过(如构造 POST 请求带 passed_captcha=true
)
这里的话还是可以通过抓包添加这个字段来提交,显示修改成功,这里的修改也是在repeater模块中修改,然后点击send
三、high级别源码分析
查看源码,服务器的验证逻辑是当$resp
是true
,并且参数recaptcha_response_field
等于hidd3n_valu3
(或者http包头的User-Agent
参数等于reCAPTCHA)
时,就认为验证码输入正确,反之错误。
<?php// 如果用户提交了 \"Change\" 表单if( isset( $_POST[ \'Change\' ] ) ) { // 隐藏 CAPTCHA 表单 $hide_form = true; // 获取用户输入的新密码和确认密码 $pass_new = $_POST[ \'password_new\' ]; // 新密码 $pass_conf = $_POST[ \'password_conf\' ]; // 确认密码 // 调用 reCAPTCHA 第三方函数验证用户输入 $resp = recaptcha_check_answer( $_DVWA[ \'recaptcha_private_key\' ], // 使用私钥 $_POST[\'g-recaptcha-response\'] // 用户输入的验证码响应 ); // 如果验证码验证成功,或使用了一个特定的隐藏值(后门验证码)并且UA是\'reCAPTCHA\'(模拟机器人) if ( $resp || ( $_POST[ \'g-recaptcha-response\' ] == \'hidd3n_valu3\' // 这是个后门值,意味着攻击者可以绕过验证码 && $_SERVER[ \'HTTP_USER_AGENT\' ] == \'reCAPTCHA\' // 并且 User-Agent 被伪装成 reCAPTCHA ) ){ // 如果两个密码相同 if ($pass_new == $pass_conf) { // 使用 mysqli_real_escape_string 防止 SQL 注入(前提是数据库连接存在) $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 ); // 使用 MD5 对密码进行哈希(不安全) // 构造 SQL 更新语句,修改当前登录用户的密码 $insert = \"UPDATE `users` SET password = \'$pass_new\' WHERE user = \'\" . dvwaCurrentUser() . \"\' LIMIT 1;\"; // 执行 SQL 语句,如果失败则输出错误 $result = mysqli_query($GLOBALS[\"___mysqli_ston\"], $insert ) or die( \'
\' . ((is_object($GLOBALS[\"___mysqli_ston\"])) ? mysqli_error($GLOBALS[\"___mysqli_ston\"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . \'\' ); // 提示用户密码修改成功 echo \"
Password Changed.\"; } else { // 如果两个密码不一致,提示错误 $html .= \"
Both passwords must match.\"; $hide_form = false; } } else { // 如果验证码验证失败,提示错误 $html .= \"
The CAPTCHA was incorrect. Please try again.\"; $hide_form = false; return; } // 关闭数据库连接 ((is_null($___mysqli_res = mysqli_close($GLOBALS[\"___mysqli_ston\"]))) ? false : $___mysqli_res);}// 生成 Anti-CSRF token,防止 CSRF 攻击generateSessionToken();?>
安全点说明:
-
✅ 高等级增加了限制:必须通过 reCAPTCHA 验证或匹配特定值且 User-Agent 正确(用于防御简单的自动脚本)。
-
❌ 后门逻辑存在:验证码绕过后门
\'hidd3n_valu3\'
+\'reCAPTCHA\'
的组合,可以被攻击者利用。 -
❌ 仍使用 MD5 加密密码:MD5 不安全,容易被破解,建议使用 bcrypt 或 Argon2。
-
❌ 数据库使用了过时接口:代码基于
mysqli_*
和老旧错误处理方式,存在维护问题。
- 由于
$resp
参数我们无法控制,所以重心放在参数recaptcha_response_field、User-Agent
上。 - 修改参数,点击
Send
:显示修改成功。
以上绕过方式确实是看了 DVWA 的源码才找到的“后门绕过条件”,这在真实世界中属于**“白盒渗透”,而大多数实际攻击是“黑盒测试”**,不能看到源码,就要靠逻辑推理 + 测试来逐步发现漏洞。在真实的 Web 安全测试中,验证码绕过是一项难度较高的工作,需要你善于分析流程、测试请求逻辑、借助自动化工具,不能依赖看源码“取巧”,而是要推演漏洞逻辑 + 试错测试。
四、impossible级别源码分析
<?php// 如果点击了“Change”按钮(提交了修改密码的表单)if (isset($_POST[\'Change\'])) { // 1️⃣ 校验 CSRF Token,防止跨站请求伪造 checkToken($_REQUEST[\'user_token\'], $_SESSION[\'session_token\'], \'index.php\'); // 表示验证码验证通过后不再显示表单(控制显示) $hide_form = true; // 2️⃣ 获取用户输入的新密码,并进行处理(去转义 + SQL安全 + 哈希) $pass_new = $_POST[\'password_new\']; $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(...)); // SQL转义,防止注入 $pass_new = md5($pass_new); // 对新密码进行 MD5 加密 // 3️⃣ 同样处理确认密码字段 $pass_conf = $_POST[\'password_conf\']; $pass_conf = stripslashes($pass_conf); $pass_conf = ((isset($GLOBALS[\"___mysqli_ston\"]) && is_object($GLOBALS[\"___mysqli_ston\"])) ? mysqli_real_escape_string($GLOBALS[\"___mysqli_ston\"], $pass_conf) : trigger_error(...)); $pass_conf = md5($pass_conf); // 4️⃣ 当前密码校验处理(确保用户是本人) $pass_curr = $_POST[\'password_current\']; $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(...)); $pass_curr = md5($pass_curr); // 5️⃣ 验证 Google reCAPTCHA,确保用户是真人操作 $resp = recaptcha_check_answer( $_DVWA[\'recaptcha_private_key\'], // 使用 DVWA 配置中的私钥 $_POST[\'g-recaptcha-response\'] // 获取用户提交的验证码响应 token ); // 6️⃣ 判断验证码是否验证通过 if (!$resp) { echo \"
The CAPTCHA was incorrect. Please try again.\"; $hide_form = false; // 显示表单重新输入 } else { // 7️⃣ 验证当前密码是否正确(从数据库中查找该用户+密码是否存在) $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(); // 8️⃣ 如果新密码一致,且当前密码正确,就更新数据库密码 if (($pass_new == $pass_conf) && ($data->rowCount() == 1)) { $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(); echo \"
Password Changed.\"; // 成功提示 } else { echo \"
Either your current password is incorrect or the new passwords did not match.
Please try again.\"; $hide_form = false; } }}// 9️⃣ 页面底部重新生成 Anti-CSRF token,用于下次提交generateSessionToken();?>
总结:Impossible级别的安全设计
安全点 说明 ✅ CSRF 防护 使用 user_token
和 session 进行校验✅ SQL 注入防护 使用 PDO
预处理语句,避免 SQL 注入✅ XSS 防护 未涉及 DOM 输出,但数据未直接回显,隐患小 ✅ CAPTCHA 验证 使用 Google reCAPTCHA,服务端校验是否通过 ✅ 身份验证 验证当前密码是否正确,防止他人盗改密码 ✅ 表单逻辑清晰 分支判断详细,失败时不暴露具体原因(只有简单提示)