DVWA从入门到精通(七):Insecure CAPTCHA(不安全的验证码)
摘要:本文是《DVWA从入门到精通》系列的第七篇,带你全面掌握Insecure CAPTCHA(不安全的验证码)模块的攻防全流程。从CAPTCHA验证码的设计初衷出发,逐步讲解Low、Medium、High三个级别的逻辑漏洞与攻击手法,并深入探讨Impossible级别的终极防御方案。文章包含两步验证流程的绕过、step参数篡改、passed_captcha参数伪造、User-Agent欺骗以及reCAPTCHA后门利用等高阶技术,让你真正做到“知其然更知其所以然”。
一、什么是CAPTCHA?
1.1 CAPTCHA的基本概念
CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart的缩写,中文意思是“全自动区分计算机和人类的图灵测试”。
简单来说,CAPTCHA就是一种用来区分用户是真人还是机器的自动化程序。
用一个生活化的例子来理解:
想象你在一个演唱会门口排队入场,保安问你:“今天星期几?”这个问题对人类来说太简单了,但对于一个没有“星期几”概念的机器人来说,它无法回答。CAPTCHA验证码就扮演着这个“保安”的角色——它设计一个对人类来说容易、但对计算机来说困难的“考题”,从而区分当前访问者是人类还是机器人。
从技术角度来看:
CAPTCHA的典型工作流程是:
服务器生成一个随机验证码(扭曲的文字、图片点选、数学题等)
服务器将验证码以图片或问题的形式展示给用户
用户输入看到的内容或回答问题
服务器验证用户输入是否与正确答案匹配
匹配则通过验证,不匹配则拒绝
1.2 CAPTCHA的作用
CAPTCHA在Web安全中扮演着重要角色:
| 应用场景 | 说明 |
|---|---|
| 防止暴力破解 | 阻止自动化工具对登录接口进行大量尝试 |
| 防止刷单/刷票 | 阻止机器人在电商平台恶意下单或投票 |
| 防止垃圾注册 | 阻止自动化脚本批量注册账号 |
| 防止评论灌水 | 阻止机器人在论坛/博客中大量发布垃圾评论 |
二、什么是Insecure CAPTCHA漏洞?
2.1 漏洞的核心本质
Insecure CAPTCHA(不安全的验证码)指的并不是验证码API本身存在漏洞,而是指在使用验证码的过程中,由于验证流程设计不当,使得攻击者能够绕过验证码的安全验证。
用一个生活化的例子来理解:
回到演唱会门口的例子。保安问“今天星期几?”你回答了“星期三”,保安说“答对了,你进去吧”。然后你进去了。
但问题是——这个保安只在你进场的时候问一次。如果你出来后再进去,保安不再问问题了,直接放行。更糟糕的是,一个没买到票的人发现了这个规律:他只需要在门口假装自己已经回答过问题,保安就放他进去了。
Insecure CAPTCHA漏洞就是这个道理——服务器只在流程的某一步检查验证码,但后续步骤不再验证。攻击者可以通过直接跳到后续步骤来绕过验证码。
2.2 漏洞的典型特征
Insecure CAPTCHA漏洞通常具有以下特征:
| 特征 | 说明 |
|---|---|
| 流程分裂 | 验证码验证和核心操作分为两个独立的步骤 |
| 状态参数可控 | 用于标识“已验证”状态的参数由客户端提交 |
| 缺乏会话绑定 | 验证结果没有与会话绑定,可以被伪造 |
| 逻辑信任错误 | 服务器无条件信任客户端声称的“已验证”状态 |
2.3 模块功能介绍
在DVWA的Insecure CAPTCHA模块中,功能是修改当前用户的密码。页面包含:
新密码输入框
确认密码输入框
reCAPTCHA验证码区域(由Google提供)
三、准备工作
3.1 靶场环境
确保DVWA已部署并正常运行:
访问地址:
http://你的服务器IP/dvwa/login.php使用
admin/password登录
3.2 reCAPTCHA密钥配置
DVWA的Insecure CAPTCHA模块使用Google的reCAPTCHA服务。在开始实验之前,需要配置reCAPTCHA的密钥。
如果未配置密钥,页面会报错,提示找不到验证码密钥。
配置方法:
访问Google reCAPTCHA管理后台:
https://www.google.com/recaptcha/admin/create注册一个新的reCAPTCHA站点,选择reCAPTCHA v2类型,Domains填写你的靶机IP
获取
site key(公钥)和secret key(私钥)
编辑DVWA的配置文件:
/xp/www/dvwa/config/config.inc.php填入密钥:
$_DVWA[ 'recaptcha_public_key' ] = '你的公钥'; $_DVWA[ 'recaptcha_private_key' ] = '你的私钥';3.3 必备工具
| 工具 | 用途 |
|---|---|
| 浏览器(Chrome/Firefox) | 访问靶场,F12开发者工具 |
| Burp Suite | 抓包分析、修改请求参数 |
四、Low级别:流程分裂的“致命漏洞”
4.1 安全级别设置
将DVWA Security设置为Low级别,然后进入Insecure CAPTCHA模块。
4.2 界面观察
页面上方显示“Password Reset”表单,包含:
New password(新密码)
Confirm new password(确认新密码)
reCAPTCHA验证码区域
4.3 源码分析
查看Low级别的核心代码:
<?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 " <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); } ?>这段代码存在致命的逻辑漏洞:
| 缺陷 | 说明 |
|---|---|
| 流程分为两步 | 第一步验证验证码,第二步修改密码 |
| 状态参数可控 | 服务器仅通过step参数判断用户是否已通过验证 |
| 第二步无验证 | step==2的代码块中没有验证码检查 |
| 信任客户端 | 服务器无条件信任客户端提交的step值 |
4.4 攻击方法:篡改step参数绕过验证码
Low级别的核心漏洞在于:step参数完全由客户端控制。攻击者可以直接将step从1修改为2,跳过验证码验证。
攻击步骤:
第一步:正常填写表单
在页面上输入新密码(如hacker123)和确认密码,然后点击提交。
第二步:使用Burp Suite抓包
Burp Suite会拦截到如下请求:
注意:请求中若无step参数,$_POST['step']为NULL;Low 级代码存在两个独立if判断:第一段要求$_POST['step'] == '1',第二段改密代码要求$_POST['step'] == '2'。NULL既不等于1也不等于2,两个判断均无法触发,不会执行密码修改逻辑。 正常前端抓包数据包携带step=1&password_new=hacker123&password_conf=hacker123&Change=Change,仅会进入验证码校验逻辑,无法直接修改密码;想要绕过验证码执行改密,需手动构造step=2的 POST 请求。
第三步:修改step=2参数
第四步:放行请求
点击“Forward”放行请求,服务器直接执行第二步的密码修改逻辑,完全跳过了验证码验证。
第五步:验证结果
页面显示“Password Changed”,密码已成功修改为hacker123。
4.5 Low级别总结
| 缺陷 | 说明 |
|---|---|
| 流程分裂为两步 | 验证与操作分离 |
step参数可控 | 攻击者可篡改 |
| 第二步无验证 | 直接执行密码修改 |
| 无CSRF Token | 可配合CSRF攻击 |
五、Medium级别:passed_captcha的“虚假安全感”
5.1 安全级别设置
将DVWA Security切换为Medium级别。
5.2 观察变化
在Medium级别下,尝试Low级别的方法(直接添加step=2),发现被阻止了。
5.3 源码分析
查看Medium级别的核心代码:
<?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 " <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); } ?>Medium级别的变化:
增加了
passed_captcha参数:第一步验证通过后,服务器生成一个包含passed_captcha=true的表单第二步检查
passed_captcha:只有passed_captcha=true时才执行密码修改本质上仍然是客户端可控:
passed_captcha参数由客户端提交
5.4passed_captcha的局限性
Medium级别的改进看起来比Low级别更安全,但本质上没有任何区别:
| 问题 | 说明 |
|---|---|
| 参数仍由客户端控制 | passed_captcha是POST参数,攻击者可以随意添加 |
| 没有服务端状态记录 | 服务器没有在Session中记录“已验证”状态 |
| 可被直接伪造 | 攻击者只需添加passed_captcha=true即可绕过 |
5.5 攻击方法:伪造passed_captcha参数
攻击步骤:
第一步:正常填写表单
输入新密码和确认密码,点击提交。
第二步:使用Burp Suite抓包
拦截到请求。
第三步:修改参数
将请求修改为:
关键点:
step=2:直接跳到第二步passed_captcha=true:告诉服务器“我已经通过验证码了”
第四步:放行请求
页面显示“Password Changed”,密码修改成功。
5.6 Medium级别总结
| 改进 | 局限性 |
|---|---|
增加了passed_captcha检查 | 参数仍由客户端控制 |
| 第二步有验证逻辑 | 验证的是客户端提交的参数 |
| 有一定防护效果 | 本质上与Low级别无异 |
六、High级别:后门与User-Agent的“诡异组合”
6.1 安全级别设置
将DVWA Security切换为High级别。
6.2 观察变化
在High级别下,尝试Medium级别的方法(添加step=2和passed_captcha=true),发现被阻止了。
6.3 源码分析
查看High级别的核心代码:
<?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'] ); 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(); ?>High级别的变化:
增加了
g-recaptcha-response检查:要求该参数等于hidd3n_valu3增加了User-Agent检查:要求
User-Agent等于reCAPTCHA这是设计者故意留下的后门(backdoor)
6.4 后门机制的原理
High级别的代码中,服务器检查:
g-recaptcha-response参数是否等于hidd3n_valu3User-Agent头是否等于reCAPTCHA
这两个值都是硬编码的,而且完全由客户端控制。
这意味着:
攻击者只需要在请求中添加
g-recaptcha-response=hidd3n_valu3并将
User-Agent修改为reCAPTCHA即可绕过验证码验证
6.5 攻击方法:伪造后门参数
攻击步骤:
第一步:正常填写表单
输入新密码和确认密码,点击提交。
第二步:使用Burp Suite抓包
拦截到请求。
第三步:修改请求
关键修改:
添加
step=2参数添加
g-recaptcha-response=hidd3n_valu3参数将
User-Agent修改为reCAPTCHA
第四步:放行请求
页面显示“Password Changed”,密码修改成功。
6.6 High级别总结
| 防御机制 | 作用 | 绕过方法 |
|---|---|---|
g-recaptcha-response检查 | 要求特定值 | 添加hidd3n_valu3 |
| User-Agent检查 | 要求特定UA | 修改为reCAPTCHA |
| 硬编码后门 | 设计者故意留下 | 知道后门值即可绕过 |
七、Impossible级别:终极防御方案
7.1 安全级别设置
将DVWA Security切换为Impossible级别。
7.2 界面观察
进入Impossible级别的页面,你会发现多了一个输入框——Current password(当前密码)。
现在修改密码需要输入三个字段:
Current password(当前密码)
New password(新密码)
Confirm new password(确认新密码)
7.3 源码分析
查看Impossible级别的核心代码:
<?php if( isset( $_POST[ 'Change' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Hide the CAPTCHA form $hide_form = true; // Get input $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_new = md5( $pass_new ); $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_conf = md5( $pass_conf ); $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_curr = md5( $pass_curr ); // 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 echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; } else { // 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 password match and was the current password correct? if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) { // Update the database $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 end user - success! echo "<pre>Password Changed.</pre>"; } else { // Feedback for the end user - failed! echo "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>"; $hide_form = false; } } } // Generate Anti-CSRF token generateSessionToken(); ?>7.4 Impossible级别的五重防御体系
Impossible级别构建了五重防御体系,彻底杜绝了验证码绕过的可能性:
第一层:CSRF Token验证
使用checkToken()函数验证请求中的user_token是否与会话中的session_token一致,防止跨站请求伪造攻击。
第二层:验证码验证(合并到主流程)
最关键的变化:验证码验证不再是一个独立的步骤,而是与密码修改合并到同一个请求中。
之前的设计:步骤1(验证码)→ 步骤2(改密码) 之后的设计:一步完成(验证码 + 改密码同时验证)攻击者无法再通过跳过步骤来绕过验证码。
第三层:当前密码验证(核心防御)
用户修改密码时,必须输入当前密码。
攻击者即使绕过了验证码,也不知道用户的当前密码,无法完成密码修改。
第四层:PDO预处理(防SQL注入)
使用PDO预处理语句执行数据库查询,彻底杜绝了SQL注入的可能性,防止攻击者通过SQL注入获取当前密码。
第五层:一次性Token刷新
每次请求后调用generateSessionToken()生成新的Token,每个Token只能使用一次。
7.5 为什么Impossible级别无法被绕过?
要成功攻击Impossible级别,攻击者需要同时满足以下条件:
| 条件 | Impossible级别的防护 | 攻击者能否达成 |
|---|---|---|
| 绕过CSRF Token | Token验证 | ❌ 无法伪造有效Token |
| 绕过验证码 | 验证码与改密合并为一步 | ❌ 无法跳过 |
| 绕过当前密码验证 | 必须输入当前密码 | ❌ 不知道密码 |
| 通过SQL注入获取密码 | PDO预处理 | ❌ 无法注入 |
| 重放Token | 一次性Token刷新 | ❌ Token已失效 |
五重防护叠加,使得Insecure CAPTCHA攻击在Impossible级别下完全不可行。
7.6 Impossible级别总结
| 防御层 | 技术手段 | 作用 |
|---|---|---|
| 第一层 | CSRF Token验证 | 防止跨站请求伪造 |
| 第二层 | 验证码与改密合并 | 消除步骤跳过漏洞 |
| 第三层 | 当前密码验证 | 即使绕过验证码也无法改密 |
| 第四层 | PDO预处理 | 防止SQL注入窃取密码 |
| 第五层 | 一次性Token | 防止请求重放 |
八、防御Insecure CAPTCHA的最佳实践
通过DVWA四个级别的对比,我们可以总结出防御验证码绕过漏洞的最佳实践:
8.1 必须实施的防御措施
| 措施 | 说明 | 优先级 |
|---|---|---|
| 验证码与操作合并 | 不要将验证码验证和核心操作分为两个步骤 | ⭐⭐⭐⭐⭐ |
| 服务端状态记录 | 在Session中记录验证状态,不依赖客户端参数 | ⭐⭐⭐⭐⭐ |
| 当前密码验证 | 修改密码等关键操作要求输入当前密码 | ⭐⭐⭐⭐⭐ |
| CSRF Token | 使用Anti-CSRF Token防止跨站请求伪造 | ⭐⭐⭐⭐⭐ |
| 服务端验证 | 所有验证逻辑必须在服务端完成,不依赖前端 | ⭐⭐⭐⭐⭐ |
8.2 推荐的辅助措施
| 措施 | 说明 | 优先级 |
|---|---|---|
| 验证码有效期 | 验证码应有有效期限制,过期失效 | ⭐⭐⭐⭐ |
| 验证码一次性 | 每个验证码只能使用一次 | ⭐⭐⭐⭐ |
| 绑定会话 | 验证码与用户会话绑定 | ⭐⭐⭐⭐ |
| 操作日志 | 记录所有敏感操作,便于审计 | ⭐⭐⭐ |
8.3 常见误区
在实际开发中,以下做法不能有效防御验证码绕过:
❌将验证码验证分为两步:攻击者可以直接跳到第二步
❌使用客户端提交的布尔值判断:如
passed_captcha=true❌硬编码后门值:如High级别的
hidd3n_valu3❌仅依赖前端验证:攻击者可以绕过前端直接发请求
❌验证码与操作分离:没有原子性保证
九、总结
本文围绕不安全验证码漏洞展开系统学习,我们先了解验证码是区分人机、抵御自动化攻击的图灵测试,明确不安全验证码漏洞根源为验证流程逻辑割裂、程序过度信任客户端可控参数,由此存在绕过风险;我们逐级完成 DVWA 各安全等级实操,Low 级别直接篡改 step 请求参数跳过验证码校验步骤,Medium 依靠客户端 passed_captcha 标记判断验证状态,该参数可直接伪造实现绕过,High 预留特殊后门,构造指定 g-recaptcha-response 参数与 User-Agent 请求头即可绕过验证码,Impossible 整合 CSRF Token、验证码与改密操作绑定、原密码校验、PDO 预处理、一次性验证码令牌五层防护,彻底消除绕过隐患;同时总结出验证码与业务操作绑定、服务端存储校验状态、关键操作原密码验证、搭配 CSRF Token 等安全防护方案。不安全验证码漏洞的核心隐患是验证逻辑分离、过度信任客户端数据,借助 DVWA 的 Insecure CAPTCHA 模块我们同步掌握各类验证码绕过攻击手法与防护逻辑,在真实生产环境中将验证码与核心操作绑定、服务端独立管理验证状态并增加原密码二次校验,多重防护结合能够从根本上避免验证码被恶意绕过。
重要声明:本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。