Insecure CAPTCHA 不安全验证码:全等级绕过与 Burp 逻辑重放实操
Insecure CAPTCHA 不安全验证码:全等级绕过与 Burp 逻辑重放实操
- 前言
- 1. 不安全验证码漏洞核心理论基础
- 1.1 什么是 CAPTCHA 验证码
- 1.2 不安全验证码漏洞本质
- 1.3 验证码漏洞的常见类型
- 1.4 漏洞危害
- 1.5 DVWA Insecure CAPTCHA 模块说明
- 2. DVWA Insecure CAPTCHA 模块(Low)
- 2.1 首先抓请求包
- 2.2 将数据包发送到Repeater
- 2.3 源码分析
- 2.3.1 整体逻辑拆分
- 2.3.2 step=1 分支完整流程
- 2.3.3 step=2 分支完整流程
- 3. DVWA Insecure CAPTCHA 模块(Medium)
- 3.1 使用Low级别的攻击方案尝试
- 3.2 源码分析
- 3.3 攻击实现
- 3.4 攻击总结
- 4. DVWA Insecure CAPTCHA 模块(High)
- 4.1 纯黑盒测试的巨大难点
- 4.2 High 级别源码完整分析
- 4.2.1 整体逻辑改动
- 4.2.2 验证码校验核心判断(漏洞关键)
- 4.3 完整攻击利用步骤
- 4.4 攻击总结
- 5. DVWA Insecure CAPTCHA 模块(Impossible)
- 5.1 安全等级说明
- 5.2 多层安全防护源码分析
- 5.2.1 CSRF 令牌校验(前置拦截)
- 5.2.2 输入数据多层过滤与转义
- 5.2.3 验证码校验逻辑(无后门、无绕过通道)
- 5.2.4 原始密码校验(身份二次鉴权)
- 5.2.5 数据库更新逻辑
- 5.3 完整防护逻辑流程
- 5.4 四级安全层级完整对比
- 免责声明
前言
在上一篇文章中,我们完整完成了 DVWA Brute Force 暴力破解模块全难度实操,熟练掌握了 Burp Suite 抓包、请求重放、Intruder 爆破模块的基础与进阶用法,理解了无防护登录接口存在的账号爆破风险。
正常场景下,网站会引入验证码机制抵御暴力猜解,阻挡批量重复请求。但很多开发者仅简单实现验证码,校验逻辑存在严重缺陷:只做前端校验、后端未核验验证码有效性、会话未销毁旧验证码等,导致验证码防护形同虚设。
本文承接前文 Burp 操作基础,针对 DVWA Insecure CAPTCHA 不安全验证码模块,逐层审计 Low/Medium/High 三级源码,演示不同层级验证码绕过思路,区分前端校验漏洞与会话复用逻辑漏洞,对比 Impossible 安全防护方案,帮你吃透验证码类 Web 逻辑漏洞的挖掘与修复思路。
📌 免责声明:本文仅用于网络安全学习与教学演示,所有实验均在本地搭建的授权靶场中进行。请勿将文中技术用于任何未授权的系统测试,违者自行承担相应法律责任。
1. 不安全验证码漏洞核心理论基础
1.1 什么是 CAPTCHA 验证码
CAPTCHA(全自动区分计算机和人类的图灵测试)的设计初衷是区分操作发起者是真人还是自动化脚本,防止机器人批量注册、暴力破解、刷票、爬虫等恶意行为。
常见形式:图形字符验证码、滑动拼图验证码、短信/邮箱验证码、Google reCAPTCHA、极验行为验证码等。
1.2 不安全验证码漏洞本质
验证码的安全核心在于:校验逻辑必须和业务操作强绑定,且校验结果不可被客户端篡改。
如果开发者把验证码校验拆成多步、把校验结果放在客户端可控的参数里、或者校验通过后没有和后续业务操作强绑定,就会出现不安全验证码漏洞——攻击者可以绕过验证码校验,直接执行敏感操作。
一句话总结:验证码形同虚设,自动化脚本可以直接绕过,防护完全失效。
1.3 验证码漏洞的常见类型
| 漏洞类型 | 原理说明 |
|---|---|
| 客户端校验绕过 | 验证码只在前端JS校验,后端不校验,直接抓包跳过前端即可绕过 |
| 校验结果可控 | 验证码是否通过由客户端参数控制(如step参数、passed_captcha标志位),篡改参数直接绕过 |
| 验证码复用 | 同一个验证码可以反复使用,不失效、不刷新,抓一次包用无限次 |
| 验证码可预测 | 验证码生成算法有规律,可被预测或枚举爆破 |
| 多步校验逻辑缺陷 | 验证码校验和业务操作拆成两步,第一步过验证码,第二步直接改参数跳业务逻辑 |
| 验证码可识别 | 图形验证码过于简单,可被OCR工具/机器学习模型自动识别 |
1.4 漏洞危害
- 暴力破解账号密码:不受验证码限制,无限次尝试登录密码
- 批量注册垃圾账号:机器人批量注册,用于刷量、发广告、薅羊毛
- 短信/邮件轰炸:无限次发送验证码,消耗服务商资源,骚扰用户
- 刷票刷量:自动化脚本批量投票、刷评论、刷积分、刷关注
- 直接执行敏感操作:绕过验证码直接改密、转账、删除数据等高危操作
1.5 DVWA Insecure CAPTCHA 模块说明
DVWA 的 Insecure CAPTCHA 模块以「修改密码」业务场景为载体,模拟了 Google reCAPTCHA 验证码的多步校验逻辑。
四个安全等级分别演示了不同程度的验证码设计缺陷:
- Low:客户端参数完全可控,直接修改 step 参数绕过
- Medium:校验逻辑存在缺陷,可被伪造绕过
- High:校验和业务绑定不严谨,存在可利用的绕过通道
- Impossible:完整正确的验证码校验逻辑,无绕过空间
接下来从 Low 级别开始,逐级抓包复现、源码审计,完整拆解验证码漏洞的利用与防御。
2. DVWA Insecure CAPTCHA 模块(Low)
2.1 首先抓请求包
开启 Burp Suite 拦截功能,页面输入新密码与确认密码(示例:123),点击Change提交表单,捕获 POST 原始请求。
观察请求参数可发现隐藏控制参数step=1,该参数用于区分业务流程阶段。
渗透测试通用测试思路:页面出现step、stage这类分段流程参数时,直接修改参数数值遍历测试:
step=0:无对应业务逻辑,页面重置;step=1:进入验证码校验流程,强制校验人机验证;step=2:直接执行密码修改逻辑,不存在验证码校验环节。
2.2 将数据包发送到Repeater
将捕获的原始请求发送至 Repeater 模块,修改请求参数:把step=1替换为step=2,其余密码参数保持不变。
可以看到这里已经成功了,回到Proxy将step改成2,然后点击Forward,将数据包发送过去
此时可以看见密码修改成功
2.3 源码分析
此处我们贴出源码
Unknown Vulnerability Source vulnerabilities/captcha/source/low.php<?phpif(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 userecho" <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 matchif($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)ordie('<pre>'.((is_object($GLOBALS["___mysqli_ston"]))?mysqli_error($GLOBALS["___mysqli_ston"]):(($___mysqli_res=mysqli_connect_error())?$___mysqli_res:false)).'</pre>');// Feedback for the end userecho"<pre>Password Changed.</pre>";}else{// Issue with the passwords matchingecho"<pre>Passwords did not match.</pre>";$hide_form=false;}((is_null($___mysqli_res=mysqli_close($GLOBALS["___mysqli_ston"])))?false:$___mysqli_res);}?>2.3.1 整体逻辑拆分
代码分为两个独立互不依赖的if判断分支,依靠客户端传入的step参数区分业务流程:
step == 1:验证码校验阶段step == 2:密码修改阶段
两段代码是并列关系,不存在先后锁死、会话标记校验,服务端完全不记录用户是否完成验证码验证,仅靠前端传参决定走哪段逻辑,这是漏洞根源。
2.3.2 step=1 分支完整流程
- 接收
password_new、password_conf、谷歌验证码返回值g-recaptcha-response; - 调用第三方谷歌接口校验验证码合法性;
- 验证码校验失败:直接输出验证码错误提示,返回页面,终止流程;
- 验证码校验成功,且两次新密码一致:
页面输出一段隐藏表单,表单内置step=2、新旧密码,提供按钮提交,进入下一阶段; - 验证码正确但两次密码不一致:返回密码不匹配提示,停留在验证码页面。
关键点:
只有正常通过验证码校验,页面才会生成携带step=2的表单,正常访问谷歌的环境下能抓到step=2请求;国内网络无法连通谷歌验证接口,验证码校验直接失败,永远不会渲染该表单。
2.3.3 step=2 分支完整流程
该分支无任何验证码相关校验代码,执行逻辑:
- 接收前端传入的
password_new、password_conf; - 仅判断两次输入的新密码是否相同;
- 密码一致:对密码做转义 + MD5 加密,执行 SQL 更新语句,修改当前登录用户数据库密码,输出
Password Changed.; - 密码不一致:返回密码不匹配提示。
致命缺陷:
程序没有设置 Session 标记记录 “用户已完成验证码校验”,攻击者可直接构造step=2的 POST 请求,跳过整个验证码校验流程,直接执行改密操作。
3. DVWA Insecure CAPTCHA 模块(Medium)
3.1 使用Low级别的攻击方案尝试
开启 Burp 拦截捕获原始提交数据包,依旧尝试将step=1修改为step=2直接发包,发现无法成功绕过。
Low 级别的单纯修改流程参数的攻击手段失效,必须结合源码审计寻找新增校验逻辑。
3.2 源码分析
对比 Low 源码,Medium 在step=2分支新增了客户端参数校验逻辑:
服务端会读取 POST 参数passed_captcha,仅当该参数值为true时,才允许执行密码更新操作。
正常流程中,验证码校验通过后页面才会自动生成携带passed_captcha=true的隐藏表单;若手动构造step=2请求却缺少该参数,后端会直接拦截改密操作。
3.3 攻击实现
- 将原始请求发送至 Burp Repeater,修改核心参数:
step=1→step=2; - 在请求末尾追加校验标识参数:
passed_captcha=true;
3. 完整 Payload:
step=2&password_new=1111&password_conf=1111&Change=Change&passed_captcha=true。
- 发送构造完成的请求,页面返回
Password Changed.,漏洞利用成功
3.4 攻击总结
漏洞成因
- 服务端仅校验前端传入的
passed_captcha参数,未使用 Session 在服务端存储验证码校验状态; - 校验标记完全由客户端可控,攻击者可手动添加该参数伪造 “已完成验证码验证” 的状态;
- 分段流程参数
step依旧无服务端状态锁,仅增加一层前端参数校验,防护存在缺陷。
4. DVWA Insecure CAPTCHA 模块(High)
4.1 纯黑盒测试的巨大难点
- Low、Medium 两种经典绕过手段全部失效
- 仅修改
step=2发包:本层级代码已完全移除 step 分段逻辑,不存在两段独立业务分支,该方法彻底无效; - 追加
passed_captcha=true参数:代码中无任何该参数的校验逻辑,添加后无任何作用; - 随意篡改、删除验证码参数:后端直接判定验证码错误,拦截改密操作。
- 仅修改
- 隐藏后门校验条件无任何前端线索
漏洞核心是后端内置了一组特殊的绕过判定,该逻辑完全写在服务端代码内,页面、请求、响应均无任何提示,纯黑盒依靠抓包试错几乎不可能猜到。
4.2 High 级别源码完整分析
4.2.1 整体逻辑改动
与 Low/Medium 最大区别:取消 step 分段流程,所有操作合并为单段代码,不再分两次提交表单,统一在一次请求内完成验证码校验 + 密码修改。
4.2.2 验证码校验核心判断(漏洞关键)
if($resp||($_POST['g-recaptcha-response']=='hidd3n_valu3'&&$_SERVER['HTTP_USER_AGENT']=='reCAPTCHA')){// 校验通过,执行改密码逻辑}校验逻辑为「或」关系,满足任意一个条件即可放行:
- 正常条件
$resp:谷歌验证码接口返回验证成功; - 隐藏后门条件:
- POST 参数
g-recaptcha-response值固定为hidd3n_valu3 - 请求头
User-Agent固定为reCAPTCHA
- POST 参数
只要同时满足以上两个头部 + 参数条件,后端直接判定验证码合法,无需真实人机验证。
4.3 完整攻击利用步骤
- 抓取原始提交数据包,原始请求携带正常表单参数;
- 修改两处关键内容:
- POST 参数新增 / 修改:
g-recaptcha-response=hidd3n_valu3 - 请求头
User-Agent修改为:reCAPTCHA
- POST 参数新增 / 修改:
- 完整请求参数示例:
step=2&password_new=1111&password_conf=1111&user_token=65189ca59a58a9b887f22fd50ce84a0b&Change=Change&g-recaptcha-response=hidd3n_valu3
- 发送构造后的请求,页面返回
Password Changed.,绕过验证码成功。
4.4 攻击总结
漏洞成因
- 开发者在后端内置硬编码后门,预留固定参数 + 请求头组合绕过验证码校验;
- 后门判断使用逻辑或
||,与正常验证码校验并列,无需真实人机验证即可放行; - 后门条件无任何前端提示,黑盒测试极难发现,仅源码审计可快速定位。
5. DVWA Insecure CAPTCHA 模块(Impossible)
此处我们贴出源码
<?phpif(isset($_POST['Change'])){// Check Anti-CSRF tokencheckToken($_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 incorrectlyecho"<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 tokengenerateSessionToken();?>5.1 安全等级说明
Impossible 为最高安全防护层级,整合多层防护机制,不存在可绕过验证码的漏洞,无法通过篡改参数、修改请求头、复用会话等方式绕过校验。纯黑盒测试无任何可利用入口,直接结合源码分层拆解安全防护逻辑。
5.2 多层安全防护源码分析
5.2.1 CSRF 令牌校验(前置拦截)
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );- 每次提交表单必须携带服务端会话生成的一次性
user_token; - Token 存储在服务端 Session,每次页面刷新会重新生成;
- 无 Token、Token 篡改、Token 过期均会直接拦截请求,杜绝批量重放、跨站伪造请求攻击。
5.2.2 输入数据多层过滤与转义
对当前密码、新密码、确认密码统一做安全处理:
stripslashes():清除输入中的转义反斜杠;mysqli_real_escape_string():特殊字符数据库转义,防SQL注入;md5():密码统一哈希加密,不存储明文;
所有用户可控输入均做净化,避免注入类漏洞。
5.2.3 验证码校验逻辑(无后门、无绕过通道)
$resp=recaptcha_check_answer($_DVWA['recaptcha_private_key'],$_POST['g-recaptcha-response']);if(!$resp){echo"<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";$hide_form=false;}仅保留唯一合法校验通道:必须调用谷歌 reCAPTCHA 第三方接口验证人机结果,移除了High级别的硬编码后门,不存在自定义参数、UA伪造绕过的逻辑分支。验证码校验失败直接终止业务流程。
5.2.4 原始密码校验(身份二次鉴权)
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );- 采用PDO预编译SQL语句,参数绑定,彻底杜绝SQL注入;
- 修改密码前强制校验当前登录账号的旧密码,仅当旧密码哈希匹配数据库记录时,才允许执行改密操作;
- 双重条件放行:
新密码一致 + 旧密码正确,任意一项不满足直接返回失败提示。
5.2.5 数据库更新逻辑
同样使用PDO预编译语句执行UPDATE更新,全程参数绑定,无字符串拼接漏洞,数据库操作安全可控。
5.3 完整防护逻辑流程
- 接收POST请求 → 校验一次性CSRF Token,非法Token直接拦截;
- 清洗、加密全部密码输入参数;
- 调用谷歌接口校验验证码,验证码错误直接返回页面;
- 验证码合法后,查询数据库校验用户输入的旧密码是否匹配;
- 同时满足「新旧两次密码一致」「旧密码校验通过」两个条件,才执行密码更新;
- 页面刷新生成全新Session Token,销毁旧令牌。
5.4 四级安全层级完整对比
| 等级 | 核心漏洞 | 绕过方式 | 防护短板 |
|---|---|---|---|
| Low | 分段step完全可控,无验证码校验 | 抓包修改step=2 | 无任何服务端校验逻辑 |
| Medium | step分段+前端参数标记校验 | step=2 & passed_captcha=true | 校验凭证存于客户端,可伪造 |
| High | 内置硬编码后门绕过验证码 | UA:reCAPTCHA + g-recaptcha-response=hidd3n_valu3 | 后端预留明文后门判断 |
| Impossible | 无可用漏洞 | 不存在有效绕过手段 | 多层安全机制闭环防护 |
免责声明
- 本文所有内容仅用于网络安全技术研究与教学演示,所有测试均在本地授权搭建的 DVWA 靶场环境中进行,旨在帮助读者理解漏洞原理与防御机制。
- 请勿将本文涉及的技术与方法用于任何未经授权的真实系统测试。任何利用本文内容进行的非法攻击行为,均与作者无关,由使用者自行承担相应法律责任。
- 网络安全学习请严格遵守《中华人民共和国网络安全法》及相关法律法规,仅在获得明确书面授权的前提下开展安全测试。
