DVWA从入门到精通(七):Insecure CAPTCHA(不安全的验证码)

发布时间:2026/7/4 4:08:36
DVWA从入门到精通(七):Insecure CAPTCHA(不安全的验证码) 摘要本文是《DVWA从入门到精通》系列的第七篇带你全面掌握Insecure CAPTCHA不安全的验证码模块的攻防全流程。从CAPTCHA验证码的设计初衷出发逐步讲解Low、Medium、High三个级别的逻辑漏洞与攻击手法并深入探讨Impossible级别的终极防御方案。文章包含两步验证流程的绕过、step参数篡改、passed_captcha参数伪造、User-Agent欺骗以及reCAPTCHA后门利用等高阶技术让你真正做到“知其然更知其所以然”。一、什么是CAPTCHA1.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 . prebr /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 prebr /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 . preBoth 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 prePassword Changed./pre; } else { // Issue with the passwords matching echo prePasswords did not match./pre; $hide_form false; } ((is_null($___mysqli_res mysqli_close($GLOBALS[___mysqli_ston]))) ? false : $___mysqli_res); } ?这段代码存在致命的逻辑漏洞缺陷说明流程分为两步第一步验证验证码第二步修改密码状态参数可控服务器仅通过step参数判断用户是否已通过验证第二步无验证step2的代码块中没有验证码检查信任客户端服务器无条件信任客户端提交的step值4.4 攻击方法篡改step参数绕过验证码Low级别的核心漏洞在于step参数完全由客户端控制。攻击者可以直接将step从1修改为2跳过验证码验证。攻击步骤第一步正常填写表单在页面上输入新密码如hacker123和确认密码然后点击提交。第二步使用Burp Suite抓包Burp Suite会拦截到如下请求注意请求中若无step参数$_POST[step]为NULLLow 级代码存在两个独立if判断第一段要求$_POST[step] 1第二段改密代码要求$_POST[step] 2。NULL既不等于1也不等于2两个判断均无法触发不会执行密码修改逻辑。 正常前端抓包数据包携带step1password_newhacker123password_confhacker123ChangeChange仅会进入验证码校验逻辑无法直接修改密码想要绕过验证码执行改密需手动构造step2的 POST 请求。第三步修改step2参数第四步放行请求点击“Forward”放行请求服务器直接执行第二步的密码修改逻辑完全跳过了验证码验证。第五步验证结果页面显示“Password Changed”密码已成功修改为hacker123。4.5 Low级别总结缺陷说明流程分裂为两步验证与操作分离step参数可控攻击者可篡改第二步无验证直接执行密码修改无CSRF Token可配合CSRF攻击五、Medium级别passed_captcha的“虚假安全感”5.1 安全级别设置将DVWA Security切换为Medium级别。5.2 观察变化在Medium级别下尝试Low级别的方法直接添加step2发现被阻止了。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 . prebr /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 prebr /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 . preBoth 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 . prebr /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 prePassword Changed./pre; } else { // Issue with the passwords matching echo prePasswords did not match./pre; $hide_form false; } ((is_null($___mysqli_res mysqli_close($GLOBALS[___mysqli_ston]))) ? false : $___mysqli_res); } ?Medium级别的变化增加了passed_captcha参数第一步验证通过后服务器生成一个包含passed_captchatrue的表单第二步检查passed_captcha只有passed_captchatrue时才执行密码修改本质上仍然是客户端可控passed_captcha参数由客户端提交5.4passed_captcha的局限性Medium级别的改进看起来比Low级别更安全但本质上没有任何区别问题说明参数仍由客户端控制passed_captcha是POST参数攻击者可以随意添加没有服务端状态记录服务器没有在Session中记录“已验证”状态可被直接伪造攻击者只需添加passed_captchatrue即可绕过5.5 攻击方法伪造passed_captcha参数攻击步骤第一步正常填写表单输入新密码和确认密码点击提交。第二步使用Burp Suite抓包拦截到请求。第三步修改参数将请求修改为关键点step2直接跳到第二步passed_captchatrue告诉服务器“我已经通过验证码了”第四步放行请求页面显示“Password Changed”密码修改成功。5.6 Medium级别总结改进局限性增加了passed_captcha检查参数仍由客户端控制第二步有验证逻辑验证的是客户端提交的参数有一定防护效果本质上与Low级别无异六、High级别后门与User-Agent的“诡异组合”6.1 安全级别设置将DVWA Security切换为High级别。6.2 观察变化在High级别下尝试Medium级别的方法添加step2和passed_captchatrue发现被阻止了。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 prePassword Changed./pre; } else { // Ops. Password mismatch $html . preBoth passwords must match./pre; $hide_form false; } } else { // What happens when the CAPTCHA was entered incorrectly $html . prebr /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这是设计者故意留下的后门backdoor6.4 后门机制的原理High级别的代码中服务器检查g-recaptcha-response参数是否等于hidd3n_valu3User-Agent头是否等于reCAPTCHA这两个值都是硬编码的而且完全由客户端控制。这意味着攻击者只需要在请求中添加g-recaptcha-responsehidd3n_valu3并将User-Agent修改为reCAPTCHA即可绕过验证码验证6.5 攻击方法伪造后门参数攻击步骤第一步正常填写表单输入新密码和确认密码点击提交。第二步使用Burp Suite抓包拦截到请求。第三步修改请求关键修改添加step2参数添加g-recaptcha-responsehidd3n_valu3参数将User-Agent修改为reCAPTCHA第四步放行请求页面显示“Password Changed”密码修改成功。6.6 High级别总结防御机制作用绕过方法g-recaptcha-response检查要求特定值添加hidd3n_valu3User-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 prebr /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 prePassword Changed./pre; } else { // Feedback for the end user - failed! echo preEither 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 TokenToken验证❌ 无法伪造有效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_captchatrue❌硬编码后门值如High级别的hidd3n_valu3❌仅依赖前端验证攻击者可以绕过前端直接发请求❌验证码与操作分离没有原子性保证九、总结本文围绕不安全验证码漏洞展开系统学习我们先了解验证码是区分人机、抵御自动化攻击的图灵测试明确不安全验证码漏洞根源为验证流程逻辑割裂、程序过度信任客户端可控参数由此存在绕过风险我们逐级完成 DVWA 各安全等级实操Low 级别直接篡改 step 请求参数跳过验证码校验步骤Medium 依靠客户端 passed_captcha 标记判断验证状态该参数可直接伪造实现绕过High 预留特殊后门构造指定 g-recaptcha-response 参数与 User-Agent 请求头即可绕过验证码Impossible 整合 CSRF Token、验证码与改密操作绑定、原密码校验、PDO 预处理、一次性验证码令牌五层防护彻底消除绕过隐患同时总结出验证码与业务操作绑定、服务端存储校验状态、关键操作原密码验证、搭配 CSRF Token 等安全防护方案。不安全验证码漏洞的核心隐患是验证逻辑分离、过度信任客户端数据借助 DVWA 的 Insecure CAPTCHA 模块我们同步掌握各类验证码绕过攻击手法与防护逻辑在真实生产环境中将验证码与核心操作绑定、服务端独立管理验证状态并增加原密码二次校验多重防护结合能够从根本上避免验证码被恶意绕过。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。