1. 项目概述与场景分析
搞Web渗透测试的人,十有八九会在登录爆破环节碰到验证码这道坎。你以为直接挂上BurpSuite Intruder就能跑字典?天真了。凡是做过几个真实目标的人都知道,正规点的系统早就把验证码校验、登录失败锁定、IP封禁这些基础防御堆满了。标题里面这个事儿——“利用BurpSuite Intruder模块自动化识别并绕过动态验证码进行表单爆破”,拆开来看其实有两层:一层是怎么让Intruder在爆破时绕过或识别验证码,另一层是自动化流程怎么做到不中断、不漏跑、不被封。
我最初做这个方向,是因为某次授权测试里遇到一个系统,密码策略极弱,但登录页加了个4位纯数字的动态验证码,刷新一次变一次。手工测了几个弱口令,效率低到崩溃。后来我把验证码识别模块和Intruder串起来,把爆破流程跑通了,整个过程才算真正有进展。这篇文章就按照我当时踩坑之后整理出来的完整思路来写,从工具原理到实操配置,再到常见问题的排查方法,尽量把能复现的细节都交代清楚。
适合看这篇文章的人有两类:一是正在学BurpSuite的渗透测试新人,看了能理解Intruder的核心机制和验证码绕过的基本套路;二是已经能跑通简单爆破,但被验证码卡住、想提升自动化程度的测试人员。文章里所有操作都基于授权测试环境,目的只有一个——在合规前提下把表单爆破这件老活儿做扎实。
2. 验证码机制拆解与识别方案选型
2.1 动态验证码的常见类型与破解难度分级
验证码设计的本质,是给自动化脚本设一道门槛。但门槛有高有低,实际测试中我按破解难度把常见的动态验证码分了四个等级,这样才好决定下一步用哪种识别方案。
第一级是4位纯数字、背景干净、无干扰线的验证码。这类验证码属于“秒杀”级别,不管是基于传统图像处理还是现在轻量级的深度学习OCR,识别率都能做到90%以上,速度几乎不影响爆破节奏。第二级是字符+数字混合、有一定扭曲但没有复杂背景的验证码,比如某些企业管理系统的登录页就是这种,识别率会掉到70%~85%,需要配合重试机制。第三级是加了噪点、干扰线、字符重叠的验证码,识别难度明显上升,这时候用通用OCR基本不靠谱,得针对目标单独训练或使用打码平台。第四级是行为式验证码,比如滑块、点选、旋转,这类已经不是图像识别能解决的问题了,Intruder直接爆破的路子走不通,通常得专门分析它的交互协议,属于高级对抗,这篇文章就不展开讲。
我建议动手前先在目标登录页刷新十次验证码,截图下来,肉眼判断它属于哪个等级。这个动作看着简单,实际上决定了你后续整个方案的走向:如果是最简单的一级验证码,干脆直接上OCR模型;如果是二级,考虑OCR配合多次重试;如果是三级,评估打码平台成本;如果是四级,就得停下来重新想思路,硬上没意义。
2.2 识别方案对比:OCR、打码平台与手工辅助
验证码识别方案的选择,本质上是“成本”和“识别率”的博弈。我把常用的三种方案列个表放在下面,方便直接对比。
| 方案类型 | 适用验证码等级 | 单次识别耗时 | 识别率 | 成本 | 自动化程度 |
|---|---|---|---|---|---|
| 本地OCR模型 | 一级、二级 | 0.5~3秒 | 70%~95% | 无直接费用,需调优 | 高,可内嵌流程 |
| 第三方打码平台 | 二级、三级 | 1~5秒 | 85%~98% | 按次收费,通常几分钱一次 | 高,API调用 |
| 手工辅助识别 | 任何等级 | 3~10秒/次 | 100% | 人力成本 | 低,半自动 |
从我个人的使用体验来说,如果是做授权渗透测试,目标验证码是一级或二级,我优先用本地OCR,原因有两点:第一,数据不出本机,不会把目标系统的验证码图像送到第三方平台去,避免数据外泄风险;第二,不依赖外部服务,爆破流程不会因为打码平台的接口波动而中断。
但本地OCR也有自己的问题——耗时间。训练一个专用模型或者调优通用模型,起步就得半天。所以如果目标验证码稍微复杂一点,或者测试工期紧,我反而建议直接用打码平台。虽然每张几分钱,但识别率稳定,整体爆破速度反而更快。
至于手工辅助识别,看着有点笨,但在某些极端情况下确实有效。比如某个目标系统的验证码有5000种以上变体,机器识别率怎么调都只有50%,这时候我会挂上半自动模式——Intruder跑到验证码生成的时候,暂停,弹窗给我看验证码,我手动输入,再继续。虽然慢,但不会卡死整个流程。
2.3 OCR识别模块的工程化封装思路
确定了用本地OCR之后,接下来要把识别模块封装成Intruder能用得起来的东西。这里有个关键认知:BurpSuite Intruder没有办法直接加载Python写的OCR脚本,它需要通过自定义扩展或者外部API服务来对接。我选择的方式是写一个本地的HTTP服务,把OCR识别逻辑包在里面,Intruder通过自定义Payload处理器的API调用来获取识别结果。
封装时有一个细节必须注意:验证码图片的预处理直接影响识别率。我踩过的坑是拿原始验证码直接丢给OCR模型,结果识别率只有40%左右。后来加了灰度化、二值化、去噪、字符分割这一整套预处理流程,识别率才拉到了85%以上。如果你打算用深度学习模型,建议不要对单个字符做分割训练,直接把整张验证码喂给模型做端到端识别,现在的主流做法都是后者,抗干扰能力更强。
我把整个OCR服务设计成了三个接口:一个是识别接口,接收验证码图片,返回识别文本;一个是健康检查接口,方便排查服务是否存活;还有一个是重试接口,当识别结果可疑时主动丢弃并请求重新获取验证码。这三个接口看起来简单,但在实际爆破过程中能省掉很多不必要的轮次,尤其是重试接口,直接决定了爆破进程会不会因为一次识别失败就整个卡住。
3. Intruder核心机制与爆破策略设计
3.1 Intruder的四种攻击模式到底怎么选
很多人只知道Intruder有四个模式,但真到了用的时候不知道该选哪个。Sniper适合单参数爆破,比如只爆破密码;Battering Ram适合所有参数用同一个payload,比如某些接口的用户名和密码一样的场景;Pitchfork适合多参数按行对应,比如你有100个用户名和100个密码要一一对应;Cluster Bomb会做笛卡尔积,比如10个用户名和100个密码组合,就会产生1000个请求。
对于标题里这个场景——表单爆破,我的经验是:如果目标是只爆破密码且用户名已知,Sniper模式加一个字典就够;如果用户名未知且密码也未知,用Cluster Bomb组合跑。但要注意,Cluster Bomb的请求量会呈指数级增长,实际测试时必须在Intruder设置里加好请求间隔,否则极其容易触发目标的频率限制。
还有一点容易被忽略:Intruder的Payload Position要标记得准。比如一个登录表单有三个变量——用户名、密码、验证码,而你只打算爆破密码,那就要把Payload Position只标记在密码这个值的位置上,用户名和验证码位置保持固定,否则每个请求都冗余携带大量数据,拖慢整个爆破速度。
3.2 Payload Processing与验证码自动刷新配合
Intruder里有一个叫Payload Processing的功能,很多人没用过,它其实是在每次请求前对payload做一次加工处理。这在验证码爆破场景下极其关键,因为验证码是一次性的,每次请求前都必须先取新的验证码图片,然后调用OCR识别,再把识别结果填入请求参数。
实际操作中,我在Payload Processing里加了一个自定义规则,指向我本地OCR服务的识别接口。具体的执行链条是这样的:Intruder每发起一次请求前,先去目标系统的验证码接口拉取一张新的验证码图片,把图片数据推送给OCR服务,OCR返回识别文本,然后Intruder把这个文本作为验证码参数填充进登录请求。整个过程完全自动,不需要人工介入,只要OCR识别率不掉链子,就可以一直跑下去。
这里要特别提醒一个细节:验证码的获取接口和登录提交接口往往是分离的,而且验证码值通常和Session ID或者Cookie绑定。如果Intruder每次请求都新建会话,就会导致验证码和登录请求不在同一个会话上下文里,后端校验必挂。我的做法是手动设置Cookie和Session保持逻辑,确保每次新请求复用同一个会话,验证码才能对得上号。
3.3 爆破节奏控制:限速、重试与并发
爆破不是越快越好。很多新手一上来就把线程调到50甚至100,结果跑了三分钟目标就把IP封了,前功尽弃。我后来学乖了,凡是真实目标系统的测试,Threads建议设置在5~15这个区间,同时开启Throttle,就是把每次请求间隔设成100到500毫秒。这个节奏下,目标系统的日志里看起来像正常用户的低频尝试,而不是机器在扫。
更关键的是重试机制。验证码识别率再怎么高,总有识别失败的时候。我希望的流程是:如果识别失败,本次请求直接放弃,不提交给目标系统,重新拉取新的验证码再试。这里要用的就是Payload Processing里的自定义处理器,或者干脆在MATCH/REPLACE规则里做二次判断。实际测试下来,识别失败率控制在15%以内时,重试机制对整体爆破速度的影响是可以接受的。
还有一个经验:Intruder的Resource Pool设置里可以控制最大并发数和请求速率,建议在爆破前先估算一下字典大小和单请求耗时。比如字典有5000个密码,单请求耗时约2秒(含验证码识别),那么总耗时就是5000乘以2约等于1.4小时,这样就可以提前规划测试窗口,避免出现跑了一下午却发现字典路径配错了的尴尬。
4. 完整实操:从请求捕获到自动爆破落地
4.1 捕获登录请求与变量定位
动手第一步是用BurpSuite代理抓登录请求。具体操作流程是:浏览器设置代理指向127.0.0.1:8080,目标系统登录页输入一个测试账号和一个测试密码,提交后在BurpSuite的HTTP History里找到这条登录POST请求。右键发送到Repeater先看看结构。
看请求时需要定位这些关键地方:请求头里的Cookie和Session字段、POST参数中用户名字段、密码字段、验证码字段、以及CSRF Token之类的隐藏字段。这里经常有个坑——很多系统登录请求里带了CSRF Token,这个Token如果不在每次爆破前更新,后端会直接拒掉所有请求。碰到这种目标,就得把获取Token的逻辑也一起写进自动化流程里,在Payload Processing里先发一个GET请求去拿Token,再填充到POST请求中。
定位完成后,把请求发送到Intruder,设置Payload Position。以我经常碰到的场景为例,一个POST请求长这样:
POST /login HTTP/1.1 Host: target.example Cookie: JSESSIONID=abc123xyz Content-Type: application/x-www-form-urlencoded username=admin&password=§pass§&captcha=§code§&csrf_token=fix_token_value密码位置标记为pass,验证码位置标记为code,用户名固定,Token固定。做完这一步,Intruder的请求模板就准备好了。
4.2 Payload配置与OCR服务对接
接下来是配置Payload。我需要在Intruder里添加两个Payload Set:第一个Set用于密码字典,加载本地密码字典文件;第二个Set用于验证码识别结果,类型选择Extension-generated,并指向我写好的自定义扩展。
这里要重点说明自定义扩展的写法。BurpSuite有官方提供的Extender API,支持Java、Python(通过Jython)等方式编写。我用的是Python,核心逻辑就是在调用generateNextPayload时,先去目标验证码接口拉取图片,然后传给本地OCR服务,拿到识别结果后返回给Intruder。另外还得维护一个全局变量,把当前会话的Cookie和验证码状态保存好,防止并发请求时上下文错乱。
OCR服务的响应格式我设计得很简单,JSON结构,包含status和code两个字段。识别成功,status为ok,code返回识别文本;识别失败,status为fail,Intruder扩展会重新拉取验证码进行下一轮尝试。这个重试逻辑尽量放在扩展内部实现,不要在Intruder主流程里频繁重试,否则并发高了反而会拖垮本地OCR服务。
4.3 请求结果判定与命中标记
Intruder跑起来之后,下一步就是在Attack窗口里观察结果,判定哪些请求是爆破成功的。不能光看HTTP状态码——很多系统即使密码错误也返回200,只是页面里有提示文案。所以正确的做法是:先用Repeater手动提交一个肯定错误的密码,记住返回特征;再提交一个正确密码(如果你有测试凭据),对比返回差异。
通常我会用两个特征点来设置匹配规则:一是响应内容长度,密码错误和密码成功时页面长度往往有明显差异;二是关键字,比如“登录成功”在源码里对应的class或提示文本。在Intruder的Options里,设置Grep-Match填入成功后的关键字,比如welcome、dashboard或者具体的成功跳转URL,这样Intruder会在结果列表里标绿,一眼就能筛出成功请求。
跑完一轮之后,建议把所有响应包含成功关键字的条目单独导出来,用Repeater逐一验证,因为偶尔会有误报。比如某些系统返回了200,页面里也确实出现了“welcome”字样,但那可能是因为验证码错误时页面里嵌了静态资源的路径。这个误判问题我遇到过不止一次,所以最终确认一定不能省。
4.4 半自动模式与异常中断恢复
自动模式跑得顺是理想情况,现实中总会遇到一些问题:识别率掉到50%以下、目标开始拒绝特定IP的请求、目标出现人机验证……这时候就得切到半自动模式。我的做法是在自定义扩展里设了一个参数,当连续五次识别失败时,扩展自动暂停Intruder的请求队列,然后弹窗提示处理人员看当前验证码截图并手工输入。这个模式在攻坚阶段特别管用。
爆破中途断掉的情况也要有预案。比如Intruder跑了40%的时候网络断了,或者代理挂了,重新跑一遍整个字典代价太高。我的习惯是跑之前先记下当前字典进度,在扩展里每100个请求写一次日志文件,记录最近完成的下标。断掉之后,从记录的下标处接着跑,不用从头再来。这个事看着不起眼,但在两三小时的爆破任务里能省出大量重复时间。
5. 常见问题与排查技巧实录
5.1 验证码识别正确但登录仍然失败的排查
最让人崩溃的Bug就是验证码明明识别对了,登录请求返回还是“验证码错误”。排查第一步:确认验证码是否与服务端当前会话绑定。很多系统的验证码存在Session里,每次拉取验证码图片后,服务器在Session里存了一份答案。如果Intruder每次请求重新创建了全新的Session,那么带着旧Cookie提交的新验证码,对后端来说就是不存在的值,自然校验失败。
排查方法很直接:在BurpSuite的Repeater里,用同一个Session的Cookie,连续提交两次“先拉验证码再登录”的请求,对比请求头里的Cookie值。如果两次Cookie不一样,就说明会话重置了。解决办法是在扩展里全局维护一个Session,拉验证码和提交登录都用同一个Cookie。
还有一种情况是前端在提交时对验证码做了加密处理。比如登录请求里的验证码字段不是明文,而是经过前端JS加密后的密文。这种隐蔽操作不抓包根本发现不了,手工复制验证码提交时是对的,但脚本里填入的是明文,后端解密失败就会报错。遇到这种情况,解决方案是把加密函数也用Python或Java重写一遍,或者直接在无头浏览器里执行前端JS完成加密,再把结果POST出来。
5.2 OCR识别率剧降的常见原因
OCR识别率突然从85%掉到30%,最多的情况是图片预处理环节出了问题。比如目标系统上线了新验证码样式,加了背景噪点或者改了字体,而预处理代码还停留在老的逻辑。建议每次爆破前都保留现场截图,包括当天的验证码样本和预处理后的图片样本。如果预处理后的图片肉眼都看不清字符,那OCR识别率低就不奇怪了。
另一个容易被忽略的原因是请求头里的Referer或User-Agent影响了验证码图片的生成。有些系统会根据UA判断访问环境,普通脚本的UA会被降级处理,返回低清晰度或大干扰量的验证码图片。解决方法是把浏览器的完整UA复制到BurpSuite的请求模板里,模拟真实浏览器请求验证码图片。
还有一个原因是OCR服务本身发生了内存泄漏或者模型污染。长跑场景下,OCR服务进程的显存或内存占用持续上涨,最终导致推理速度越来越慢,甚至返回空结果。我的做法是在OCR服务里加一个计数器,每处理5000张图片自动重启一次模型加载,清空缓存。这个经验是长期跑自动化爆破后总结出来的,基本能保证识别率不掉。
5.3 目标触发锁定机制后的应对策略
爆破过程中最怕的不是验证码,而是触发账号锁定或IP封禁。目标系统如果设置了连续5次登录失败锁定账号,那你自动化跑两组密码后账号就锁了,密码再正确也登录不上去。面对这种情况,爆破策略必须调整:不爆破固定用户名,改成“每用户只试两个常用密码,轮换大量用户名”,这种喷洒(Password Spraying)思路能有效规避账号锁定。
对于IP封禁,更简单的应对是挂代理池。我在实战中会用多个代理轮流发包,每个代理的请求量控制在触发阈值以内。但注意,并不是所有测试场景都用代理池——加代理会增加网络延迟,降低爆破效率。所以只有在目标明确存在IP封禁机制时才启用,平时做好限速就够了。
还有一种更微妙的情况:目标系统只封特定路径的IP,但静态资源仍然放行。这种时候,爆破请求会返回302跳到验证页面,导致Grep-Match的匹配规则失效。排查思路是看爆破进行中响应长度是否出现了统一的“平线”,如果连续几十个请求都在同一奇怪的长度,大概率是跳转到了人机验证页面。此时应该停下,手工刷新一次浏览器,走完人机验证,拿到新的Cookie再继续。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 验证码识别没错但登录失败 | Session未复用 | 对比Repeater中的Cookie是否一致 | 扩展中全局维护固定Session |
| 请求全返回错误验证码 | 前端对验证码加密后再提交 | 抓包检查登录提交字段是否为明文 | 重写加密函数或调用无头浏览器 |
| OCR识别率骤降 | 目标验证码样式更新或图片预处理失效 | 检查预处理后的图片样本 | 重新调优预处理流程或重新训练模型 |
| 目标响应均匀变长 | 触发了人机验证,请求被重定向 | 观察响应长度曲线是否出现平线 | 手工过验证,换新Cookie续跑 |
| 账号被锁导致后续爆破无效 | 连续失败触发锁定策略 | 查看系统登录响应是否有锁定提示 | 改为密码喷洒模式,控制每用户尝试次数 |
| OCR服务越来越慢 | 内存泄漏或模型缓存未清 | 监控OCR服务内存占用 | 加入定时模型重载机制 |
5.5 实操心得:字典质量比工具更影响结果
最后说一个容易被忽视的点:Intruder再强,密码字典不行,爆破结果也白搭。我见过很多人直接把网上流传的弱口令字典拖进去跑几千个,跑完发现目标密码根本没在字典里。我的做法是先用目标公司的信息收集结果定制字典——比如域名、电话区号、年份、员工姓名等方式组合生成一批候选密码,再叠加通用弱口令。这类定制字典往往几十条就能命中,比跑几万行的通用字典高效得多。
组合密码生成这事,BurpSuite的Payload Processing里也支持规则拼接,比如把年份加在常见单词前后。但要注意,Intruder的Payload Processing里做拼接,成本是按组合数算的,很容易膨胀成几十万条。所以我的建议是,不要依赖Intruder做智能生成,而是用自己写的脚本在本地生成好字典文件,再导入Intruder跑。这样既能控制字典规模,还能提前做好去重,跑起来更快。