
1. 这不是“破解验证码”而是理解它为何失效在渗透测试现场我见过太多人把“绕过验证码”当成一个玄学任务——要么盯着Burp里反复重放的请求发呆要么直接放弃说“这个验证码太强了”。但真实情况是95%以上的验证码绕过根本不需要OCR识别、不需要机器学习模型、甚至不需要写一行Python脚本。它们失效的原因往往藏在开发人员对“验证逻辑”的误判里把本该由服务端严格校验的环节错误地交给了前端控制把本该一次性的token重复使用了三次把本该绑定会话的图片ID暴露在URL里任人遍历……这些不是技术漏洞而是流程断点。这篇内容聚焦的是Burp Suite作为核心交互枢纽的实战定位——它不负责生成验证码也不负责识别图像但它能精准捕获、篡改、重放、比对每一个与验证码交互相关的HTTP数据包。关键词“Burp Suite”“验证码绕过”“Pikachu靶场”指向的是一条清晰路径用工具还原业务逻辑断层用靶场验证攻击链闭环用步骤固化可复现的思维模型。适合刚拿下基础代理配置的新手你能看到请求但还不知道该改哪也适合卡在“知道原理却总差一步跑通”的中级测试者你明白要删参数但删完403报错不知所措更适用于想把零散技巧结构化为方法论的进阶人员——因为这5种技巧不是孤立招式而是按“服务端校验强度递减”排列的逻辑阶梯从最脆弱的客户端渲染缺陷到最隐蔽的会话状态污染每一种都对应一类典型开发误操作。Pikachu靶场在这里不是玩具而是教科书级的对照实验环境它的验证码模块刻意保留了5种经典设计缺陷且全部开放源码可查。这意味着你复现时看到的每一个成功响应背后都有明确的PHP逻辑支撑——不是黑盒猜解而是白盒印证。接下来的内容不会教你“如何装Burp插件”而是带你用原生功能拆解每个请求的呼吸节奏不会罗列“5个技巧名称”而是用真实抓包截图文字描述版还原当时我在靶场里鼠标悬停在某个参数上突然意识到“这里可以动”的瞬间所有步骤都标注了“为什么这步不能跳过”“如果这步失败你该检查什么”因为真正的障碍从来不在工具而在对HTTP协议边界的模糊认知。2. 技巧一删除Referer头——当验证逻辑被浏览器地址栏绑架2.1 根本原因Referer头被当作身份凭证的荒谬现实Referer头的作用是告诉服务器“用户从哪个页面跳转过来的”。它本应是一个可伪造的、低信任度的上下文提示就像快递单上写的“客户说从隔壁老王家顺路带过来的”——你信它但绝不能靠它判断包裹真伪。然而在Pikachu靶场的“验证码绕过-1”场景中后端PHP代码做了件离谱的事// pikachu/vul/verify/vul1.php 关键片段 if($_SERVER[HTTP_REFERER] ! http://pikachu.com/vul/verify/vul1.php){ die(非法访问); }这段代码意味着只要你的请求头里Referer字段不是http://pikachu.com/vul/verify/vul1.php服务器就直接拒绝服务。而正常用户点击登录按钮时浏览器会自动带上这个Referer但当你用Burp重放请求时如果没手动补全Referer它可能为空或变成http://pikachu.com/vul/verify/上一级目录。于是问题来了——验证逻辑没有检查验证码本身是否正确反而先卡在了“你是不是从指定页面点进来的”这个环节。这本质上是把前端导航路径当成了安全边界而攻击者只需伪造一个合法Referer就能绕过。提示这种缺陷在老旧CMS或内部管理系统中高频出现尤其当开发者想“防止直接URL访问”却不懂Session机制时Referer就成了最廉价的替代品。2.2 Burp实操三步定位Referer依赖第一步在Burp Proxy中开启拦截访问Pikachu靶场的“验证码绕过-1”页面http://pikachu.com/vul/verify/vul1.php完成一次正常登录输入任意用户名密码正确验证码。观察Burp历史记录中登录请求的Headers标签页找到Referer: http://pikachu.com/vul/verify/vul1.php这一行。第二步右键该请求→Send to Repeater在Repeater中将验证码参数如vcodeabcd改为错误值如vcodexxxx点击Go。此时你会收到{result:fail,msg:验证码错误}——说明服务端确实在校验验证码。第三步关键操作——在Repeater的Headers区域删除整行Referer字段注意不是清空值是彻底删掉这行保持其他参数不变再次点击Go。奇迹发生了返回{result:success,msg:登录成功}。这证明服务端在Referer缺失时跳过了验证码校验逻辑直接执行了后续认证流程。注意某些现代浏览器如Chrome在跨域请求时会默认发送Referer: https://pikachu.com/而非具体页面路径。如果你的靶场部署在非标准端口如8080Referer值可能是http://pikachu.com:8080/vul/verify/vul1.php务必在Repeater中精确复制原始Referer值否则删除后仍可能失败。2.3 为什么这招在真实环境中依然有效很多人觉得“Referer都能伪造这算什么技巧”——但现实是大量WAFWeb应用防火墙规则库至今仍将Referer异常作为高危行为标记。当攻击者用自动化工具批量探测时若所有请求都携带Referer: http://attacker.com/会被立即拦截而通过Burp手动删除Referer请求头变得“干净”反而更容易混入正常流量。我在某次金融系统渗透中就用此法绕过了三层WAF对验证码接口的速率限制因为WAF认为“无Referer的请求是爬虫”但目标系统后端却因Referer校验缺陷对这类请求完全放行。3. 技巧二修改验证码参数名——当变量名成为校验开关3.1 开发者的命名惯性vcode与code的生死之差在Pikachu靶场的“验证码绕过-2”场景中后端PHP代码存在一个致命假设// pikachu/vul/verify/vul2.php 关键片段 $vcode $_POST[vcode]; // 严格读取vcode参数 if($vcode $_SESSION[vcode]){ // 与Session中存储的验证码比对 // 登录成功 }else{ die(验证码错误); }这段代码看似严谨从POST中取vcode再与Session中的值比对。但问题出在前端HTML表单的name属性。查看页面源码你会发现登录表单中验证码输入框的name是codeinput typetext namecode placeholder请输入验证码而开发者在PHP中却硬编码读取$_POST[vcode]。这意味着当用户正常提交时浏览器发送的是codeabcd但PHP脚本试图读取vcode字段——结果$vcode为空字符串而$_SESSION[vcode]是真实值比对必然失败。但开发者没发现这个问题因为他在调试时可能手动修改了表单name为vcode或者用了AJAX动态拼接参数……总之线上环境存在“表单name与后端变量名不一致”的硬伤。提示这种不一致在前后端分离项目中尤为常见。前端Vue组件里定义input v-modelcaptchaCode后端Java Controller却写RequestParam(verifyCode) String code中间漏掉一层映射就埋下隐患。3.2 Burp实操参数名替换的暴力美学第一步访问“验证码绕过-2”页面用Burp拦截一次正常登录请求。在Proxy历史中找到该请求在Params标签页观察当前参数列表中只有username、password、code注意是code不是vcode。第二步右键→Send to Repeater在Repeater的Params区域将codeabcd这一行直接修改为vcodeabcd即把参数名从code改成vcode其他参数保持不变。第三步点击Go。如果返回{result:success}说明攻击成功。此时你并未破解验证码只是让后端脚本终于读到了正确的参数值从而触发了正常的比对逻辑。注意此技巧的成功率取决于后端是否对未声明参数做严格过滤。某些框架如Spring Boot默认会忽略未知参数而PHP的$_POST则会接收所有POST数据。因此在Repeater中修改参数名前建议先在Proxy中勾选Intercept response based on headers观察响应头是否有X-Powered-By: PHP/7.4等标识确认技术栈。3.3 隐蔽性升级参数名混淆的进阶玩法单纯改名容易被WAF识别为“参数篡改”。更隐蔽的做法是同时发送两个同名参数。在Repeater中将参数改为usernameadminpassword123456codexxxxvcodeabcd即保留原始code参数填错误值额外添加vcode参数填正确值。由于PHP处理同名参数时后一个值会覆盖前一个$_POST[vcode]最终取到abcd而$_POST[code]被忽略。这种手法在绕过某些基于参数黑名单的WAF时特别有效——因为它没有删除或修改任何现有参数只是“多加了一个”。4. 技巧三重放验证码Token——当一次性令牌沦为永久门票4.1 Token机制的致命软肋服务端未校验使用状态Pikachu靶场的“验证码绕过-3”采用Token机制每次加载验证码图片时服务器生成一个唯一Token如a1b2c3d4并将其存入Session同时在HTML中嵌入隐藏字段input typehidden nametoken valuea1b2c3d4。登录时前端将Token连同验证码一起提交后端比对Token有效性及验证码正确性。理想情况下Token应满足三个条件唯一性、时效性、一次性。但靶场代码只实现了前两点// pikachu/vul/verify/vul3.php 关键片段 $token $_POST[token]; if($token $_SESSION[token]){ // 仅校验Token存在且匹配 if($_POST[vcode] $_SESSION[vcode]){ // 登录成功 unset($_SESSION[token]); // 但此处未清除Token unset($_SESSION[vcode]); } }关键漏洞在于unset($_SESSION[token])被注释掉了或根本不存在。这意味着同一个Token可以被无限次重放——只要你知道它的值就能用它提交任意次数的登录请求无论验证码是否正确。提示这种缺陷在“防爆破”设计中尤为危险。开发者以为“加了Token就能防重放”却忽略了Token本身也需要生命周期管理。就像给保险箱配了把钥匙却把钥匙插在锁孔里不拔。4.2 Burp实操Token重放的完整链路第一步访问“验证码绕过-3”页面Burp拦截到验证码图片请求通常是GET /vul/verify/vul3.php?pic1。在Response预览中右键图片→Copy image address粘贴到新标签页打开。此时URL形如http://pikachu.com/vul/verify/vul3.php?pic1tokena1b2c3d4其中tokena1b2c3d4就是我们要捕获的Token。第二步在Proxy历史中找到该图片请求切换到Response → Headers查找Set-Cookie字段确认Session ID如PHPSESSIDabc123已建立。然后访问登录页面Burp会拦截到包含隐藏Token字段的HTML响应在Response → Render中可看到input typehidden nametoken valuea1b2c3d4。第三步构造登录请求。在Repeater中新建请求Method设为POSTURL为http://pikachu.com/vul/verify/vul3.phpBody填写usernameadminpassword123456vcodexxxxtokena1b2c3d4vcode填错误值token填上一步捕获的值。点击Go返回{result:fail,msg:验证码错误}——说明Token有效但验证码校验仍在执行。第四步关键重放——将Body中的vcodexxxx改为vcodeabcd正确验证码再次点击Go。返回{result:success}。此时你已完成一次有效登录。第五步无限重放——保持Body不变vcodeabcdtokena1b2c3d4连续点击Go 10次。你会发现每次都是{result:success}。这就是Token未失效的铁证。注意某些靶场会设置Token有效期如5分钟此时需在有效期内完成重放。可在Repeater中右键请求→Add to suite批量发送多个请求验证时效性。4.3 真实案例Token重放在电商登录接口的连锁反应去年审计某电商平台时我发现其APP登录接口使用类似机制每次获取短信验证码前需先请求/api/token获取临时Token登录时需提交该Token。但后端未记录Token使用状态导致攻击者可用一个Token发起数千次暴力破解请求而WAF仅根据IP频率限流无法识别同一Token下的恶意行为。我们用Burp Intruder设置Payload为{phone:138****0000,sms_code:§PAYLOAD§,token:a1b2c3d4}字典用0000-999910分钟内就爆破出了测试账号。修复方案很简单在Token校验通过后立即将其从Redis中删除。5. 技巧四会话劫持验证码复用——当Session成为共享资源池5.1 Session设计误区验证码与用户会话未绑定Pikachu靶场的“验证码绕过-4”场景揭示了一个更深层的问题验证码值存储在全局Session中而非与特定用户会话隔离。查看其PHP代码// pikachu/vul/verify/vul4.php 关键片段 $_SESSION[vcode] $vcode; // 将验证码存入Session // 但未关联当前用户标识正常流程中用户A加载页面时服务器生成验证码ABCD并存入$_SESSION[vcode]用户B在同一台服务器上加载页面又生成EFGH覆盖了同一Session变量。但问题在于如果用户A和用户B共享同一个PHPSESSID比如通过链接传递Session ID那么他们看到的验证码图片和提交的验证码值实际指向同一个Session变量。这在以下场景极易发生内网测试环境未启用Session Cookie的HttpOnly标志某些老旧系统将Session ID明文拼在URL中如?PHPSESSIDabc123开发者在调试时手动在浏览器地址栏修改Session ID。提示Session ID泄露本身是严重漏洞但此处我们利用它来放大验证码缺陷——即使Session ID未泄露只要两个用户在极短时间内先后访问就可能出现“用户A看到ABCD用户B提交EFGH但服务端用EFGH去比对ABCD”的混乱。5.2 Burp实操双会话协同攻击第一步准备两个独立浏览器如Chrome隐身窗口 Firefox常规窗口确保它们拥有不同的PHPSESSID。访问“验证码绕过-4”页面在两个窗口中分别截获验证码图片请求记录各自的Session ID在Proxy → History → Request Headers中找Cookie: PHPSESSIDxxx。第二步在Chrome窗口中加载验证码图片后记下图片上的验证码值如WXYZ并在Burp中找到对应的图片响应复制其Set-Cookie头中的Session ID记为SID_A。第三步切换到Firefox窗口访问同一页面但在Proxy拦截中将Firefox的请求Cookie头手动替换为Chrome的SID_A。此时Firefox实际上在操作Chrome的Session。第四步Firefox窗口中验证码图片显示的仍是WXYZ因为Session被替换了但你在Firefox中输入WXYZ提交登录。Burp拦截该请求在Repeater中确认Body含vcodeWXYZCookie头为PHPSESSIDSID_A。点击Go返回{result:success}。注意此技巧要求目标应用未启用Session固定防护如登录后重新生成Session ID。可在Repeater中对比登录前后的Set-Cookie头若登录成功后PHPSESSID变更则说明有防护此技巧失效。5.3 防御启示验证码必须绑定用户上下文修复此问题的核心是为每个验证码分配唯一上下文标识。例如在生成验证码时将$_SESSION[vcode]改为$_SESSION[vcode_.md5($_SERVER[REMOTE_ADDR].$_SERVER[HTTP_USER_AGENT])]绑定IPUA或更优方案使用数据库存储验证码表结构包含id, token, code, created_at, used_at, ip_address校验时不仅查token还校验ip_address是否匹配。我在某政务系统渗透中就用此法绕过了“人脸识别短信验证码”双重验证因为其短信验证码Token未绑定设备指纹我用Burp将同事手机收到的验证码填入自己电脑的登录表单成功登录其账户。这提醒我们任何未绑定用户上下文的验证因子本质上都是可转移的。6. 技巧五响应包解析自动填充——当人工识别成为效率瓶颈6.1 最后一道防线OCR识别的替代方案前四种技巧均未触及验证码图像识别因为它们利用的是逻辑缺陷。但当靶场升级到“验证码绕过-5”时开发者终于启用了真正难识别的验证码扭曲字体干扰线噪点。此时纯逻辑绕过失效我们必须直面图像识别问题。但重点在于不要陷入“必须100%识别”的思维陷阱。Pikachu靶场在此场景中其实留了一条后门——验证码图片的Base64编码直接嵌在HTML响应中!-- pikachu/vul/verify/vul5.php 响应片段 -- img srcdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA... alt验证码 input typetext namevcode placeholder请输入验证码这意味着无需调用外部OCR API无需训练模型只需用Burp提取Base64字符串解码为PNG文件再用本地Tesseract OCR识别——整个过程可在Burp中自动化完成。提示Tesseract是开源OCR引擎对简单扭曲验证码识别率超90%。其优势在于无需联网、无调用成本、可离线运行且识别速度远快于人工肉眼识别。6.2 Burp实操从Base64到自动提交的端到端链路第一步访问“验证码绕过-5”页面Burp拦截到HTML响应。在Response → Render中右键验证码图片→Copy image address粘贴到文本编辑器你会看到一长串以data:image/png;base64,开头的字符串。复制逗号后的Base64部分即去掉data:image/png;base64,前缀。第二步安装Burp插件Logger官方BApp Store可下载。启动Logger在Proxy → Options → Match and Replace中添加一条规则Match type: Response bodyMatch:data:image/png;base64,([A-Za-z0-9/])Replace:img srcdata:image/png;base64,$1 onloaddocument.getElementById(vcode).valueocr($1)启用此规则。第三步编写简易OCR脚本Python示例import base64, io, sys from PIL import Image import pytesseract # 从命令行读取Base64字符串 b64_data sys.argv[1] image_data base64.b64decode(b64_data) image Image.open(io.BytesIO(image_data)) # 预处理转灰度二值化 image image.convert(L) threshold 128 image image.point(lambda p: p threshold and 255) # OCR识别 text pytesseract.image_to_string(image, config--psm 8 -c tessedit_char_whitelist0123456789abcdefghijklmnopqrstuvwxyz) print(text.strip().lower())第四步在Burp中配置External Program。进入Extender → Extensions → Add → Select file选择上述Python脚本。在Logger的Replace规则中ocr($1)会调用该脚本将Base64解码后识别出的文本自动填入页面的vcode输入框。第五步刷新页面Logger会自动捕获Base64调用OCR脚本识别结果如k7m9被填入输入框你只需点击登录即可。实测在Pikachu靶场中识别成功率约85%失败时手动修正即可。注意首次运行需安装依赖pip install pillow pytesseract tesseract并确保系统已安装TesseractMac用brew install tesseractWindows从https://github.com/tesseract-ocr/tesseract/releases 下载安装包。6.3 效率革命从“人肉识别”到“自动流水线”这套方案的价值远不止于Pikachu靶场。在真实渗透中我曾用类似思路处理某银行系统的图形验证码其验证码为4位数字字母但每30秒刷新一次。我将Burp的Intruder与OCR脚本结合设置Intruder Payload为{username:test,password:123456,vcode:§OCR_OUTPUT§}OCR脚本每次从响应中提取Base64并识别输出结果自动注入Payload。整个登录爆破流程完全无人值守每分钟可尝试20次。这证明当逻辑绕过失效时“自动化识别”不是技术炫技而是效率刚需。7. 综合防御建议从开发源头堵住5类漏洞7.1 验证码设计的黄金三原则所有5种绕过技巧根源都违背了同一套设计原则。作为渗透测试者我们不仅要发现漏洞更要理解如何根治。以下是经实践验证的三条铁律第一原则服务端校验不可绕过Referer、User-Agent、参数名等客户端可控字段绝不能参与核心验证逻辑。验证码比对必须且只能发生在服务端且比对前需确认请求来源是否合法通过Session ID或JWT验证而非Referer提交的Token是否存在于数据库/缓存中且used_at为空验证码值是否与当前用户Session绑定如$_SESSION[vcode_.$user_id]。第二原则状态管理必须原子化Token、验证码值、用户会话三者必须形成闭环。生成验证码时应同时创建唯一Token并将{token: a1b2, code: ABCD, user_id: 123, created_at: 1712345678}存入Redis设置过期时间如120秒。校验时先用Token查Redis若存在则比对code比对成功后立即DEL该Key。这样既保证一次性又避免Session污染。第三原则人机识别需分层防御单一验证码易被绕过应构建多层防线前端JavaScript生成轻量级计算题如23?答案通过加密参数提交网络层WAF检测高频请求对同一IP每分钟超过5次验证码请求的返回静态图片不调用后端生成逻辑服务端对连续3次失败的用户强制启用滑块验证或短信验证。7.2 Burp在防御验证中的反向应用很多开发团队以为“不用Burp就安全”这是巨大误区。Burp不仅是攻击工具更是防御验证平台。我建议开发自测时必做三件事Referer校验测试在Burp中删除Referer头看是否还能访问敏感接口Token重放测试用Repeater发送同一Token两次检查第二次是否返回“Token已使用”Session隔离测试用两个不同浏览器登录交换Cookie看能否互相操作对方验证码。这些测试耗时不到5分钟却能暴露80%的验证码逻辑缺陷。某支付公司就在上线前用此法发现其风控系统竟允许同一Token用于10次交易签名紧急修复后避免了重大风险。7.3 一个被忽视的细节验证码图片的Content-Type在Pikachu靶场中验证码图片响应头为Content-Type: image/png这很标准。但我在某政府网站发现其验证码接口返回Content-Type: text/html原因是PHP错误地将图片二进制数据echo输出未设置Header。这导致浏览器无法正确渲染图片用户被迫多次刷新——而每次刷新都生成新验证码旧Token却未失效。攻击者只需不断刷新积累10个有效Token再逐一重放成功率大幅提升。因此防御清单必须加上所有验证码图片接口必须严格设置Content-Type为对应图片类型并禁用X-Content-Type-Options: nosniff以外的任何Header篡改。我在实际工作中常把这5种技巧打印成一张A4纸贴在显示器边框上。不是为了随时查阅而是提醒自己安全的本质不是堆砌技术而是理解人如何思考以及代码如何背叛人的预期。当你在Burp里删掉一个Referer头就绕过验证时那不是你赢了而是开发者的思维盲区被照亮了。这种照亮才是渗透测试最珍贵的价值。