BurpSuite插件联合本地OCR:自动化突破登录验证码识别实战 1. 项目概述登录接口安全测试的“最后一公里”做过接口安全评估的人都有体会登录框是攻防博弈的前线而验证码往往是拦在自动化测试工具面前的第一道墙。很多安全测试场景中我们需要在授权范围内验证登录接口的认证强度、账号锁定策略、风控触发阈值但图形验证码的存在让BurpSuite这类工具的自动化测试流程频频卡壳——不是工具不够强而是验证码识别这个环节没打通。这个项目的本质是把“验证码识别”从人工肉眼辨认的拖沓流程中解放出来用本地OCR服务配合BurpSuite扩展插件构成一套完整可复用的自动化测试链路。组合拳的核心思路非常直接BurpSuite负责发送请求、接收响应、管理测试用例本地OCR负责把响应里返回的验证码图片转成文本再通过插件把识别结果回填到下一次请求中。三者各司其职形成闭环。这个方案适合三类人一是做授权渗透测试的安全工程师在客户授权范围内评估登录接口的暴力破解防护能力二是做接口自动化测试的开发或测试人员需要批量验证登录接口在各种异常输入下的表现三是安全爱好者想搞明白验证码在技术层面上是如何被“机器”读懂的以及如何针对性加固。我在自己的测试环境中完整走了一遍流程从OCR服务搭建到插件配置再到联调排错前后花了大半天时间。这篇就把完整的配置流程、踩过的坑、关键参数的选择逻辑一并整理出来。2. 技术选型背后的逻辑为什么是“本地OCR插件”2.1 方案对比云识别、本地OCR、人工介入先说说为什么最终选了“本地OCR插件”这个组合而不是其他方案。实际在验证码识别这件事上有几种常见路径云识别平台准确率高、使用简单但需要把验证码图片上传到第三方服务。在授权测试场景下目标系统的验证码图片本身可能包含敏感信息或内部业务特征把图片发到外部平台存在数据外泄风险合规性上站不住脚。纯人工介入肉眼识别后手动填入安全合规但效率极低。如果测试用例有几百上千条人工介入会让整个流程变得不可用。本地OCR模型部署在自己机器上数据不出本地识别速度取决于本地算力。对于常见的数字、字母组合型验证码准确率可以做到很高而且响应时间通常在毫秒级。BurpSuite插件直连OCR服务这是把前面所有环节串起来的关键——OCR识别得再快如果不能自动回填到BurpSuite的请求中那整个流程依旧是断的。对比下来本地OCR在“安全合规”和“识别效率”之间取得了最好的平衡。而BurpSuite插件作为粘合剂恰恰解决了“识别结果如何自动进入测试流程”这个核心痛点。2.2 核心组件拆解OCR侧与Burp侧职责划分这套组合拳由两大块组成职责划分非常清晰OCR侧负责“看图识字”。它接收验证码图片输出识别文本。具体来说需要搭建一个本地HTTP服务暴露一个接口上传图片返回识别结果。这个服务可以用现成的OCR引擎封装也可以用深度学习模型单独训练。BurpSuite侧负责“发请求、取图片、回填答案”。通过自定义插件BurpSuite能够拦截登录请求的响应从中提取验证码图片的URL或Base64数据把图片数据POST到本地OCR服务获取返回的识别文本将其填入下一个登录请求的验证码字段。两个组件之间通过HTTP接口通信彼此独立、耦合度很低。OCR服务可以独立部署在任意机器上BurpSuite插件则只需要配置OCR服务的地址即可。这种设计的直接好处是后续想更换更强的OCR引擎只需要保证接口协议不变插件代码零改动想扩展到多线程高并发识别直接在OCR服务端做负载均衡就行不需要动BurpSuite。2.3 为什么推荐本地部署而非内嵌识别有一种思路是把OCR引擎直接内嵌进BurpSuite插件里——通过JAVA调用本地OCR库省掉HTTP中间层。我试过这种方式但最终放弃了原因有三第一耦合度太高。BurpSuite插件跑在JVM里OCR模型往往依赖Python生态环境强行内嵌要么用JNI桥接要么把Python模型打包成独立可执行文件每一步都在跟版本兼容性搏斗。第二独立服务更容易调试。本地OCR服务和BurpSuite插件分开之后任何一环出问题都能独立验证——用curl直接请求OCR接口就能判断问题出在识别环节还是通信环节不用每次都打开BurpSuite跑一遍完整流程。第三灵活部署。OCR服务部署在一台单独机器上BurpSuite跑在另一台机器上这在分布式测试环境里特别实用——正好也说明HTTP接口方案比内嵌方案更具扩展性。注意本地部署指的是一切都在授权测试者自己的受控环境中运行并不涉及任何第三方外部服务调用这一点在合规层面有重要意义。3. 环境搭建与依赖准备3.1 软硬件环境清单我用的测试环境是Windows 11宿主机加一套独立的Linux虚拟机整体结构如下宿主机Windows 11运行BurpSuite负责发起和接收测试请求JAVA运行时用于BurpSuite插件运行。虚拟机Ubuntu 22.04 LTS运行本地OCR服务纯Python实现使用FastAPI框架封装HTTP接口模型推理使用CPU即可无需GPU。网络宿主机与虚拟机通过仅主机模式互通固定IP为192.168.56.101。这个“物理分离”的好处是即使OCR服务崩了也不影响BurpSuite主体运行识别请求和测试请求的网络路径分开互不干扰。3.2 OCR服务端的依赖安装OCR服务侧的核心依赖如下。# Ubuntu 22.04 系统层依赖 sudo apt update sudo apt install -y tesseract-ocr libtesseract-dev libleptonica-dev # Python 3.10 环境依赖 pip install fastapi uvicorn pillow pytesseract requests这里用到了Tesseract作为基础OCR引擎加上FastAPI做HTTP服务封装。如果验证码是纯数字、纯字母或者数字字母组合Tesseract在简单预处理后就能达到不错的识别率。如果你的验证码扭曲、干扰线严重可以考虑换成PaddleOCR或者专门训练的目标检测识别模型。但从“快速搭建、跑通流程”的角度讲Tesseract是目前性价比最高的选择——因为它不需要训练过程装完即用。3.3 BurpSuite插件框架准备BurpSuite插件用JAVA开发基于官方的Montoya API较新版或传统API旧版本。我这里用的是BurpSuite 2023.12版本支持Montoya API。插件的核心逻辑只需要实现三个接口方法onStart插件加载时执行注册HTTP处理器和上下文菜单onHttpRequest请求发出前拦截把OCR识别结果填入验证码字段onHttpResponse响应返回后处理提取验证码图片数据并触发识别。开发环境只需要一个JAVA IDE和对应版本的BurpExtender API依赖即可。我在实际开发中用的是Gradle构建打包成jar后直接在BurpSuite中加载。4. 本地OCR服务端完整实现4.1 FastAPI服务搭建接口设计与代码实现先上完整代码再做关键点说明。以下是ocr_server.py的完整实现。import base64 import io import re import time import pytesseract from fastapi import FastAPI, File, Form, UploadFile from PIL import Image, ImageEnhance, ImageFilter, ImageOps from pydantic import BaseModel app FastAPI(titleLocal OCR Service, version1.0.0) class OCRResponse(BaseModel): text: str cost_ms: int success: bool def preprocess_image(img: Image.Image) - Image.Image: 验证码图片预处理灰度化、二值化、去噪点、增强对比度。 这一步对Tesseract识别率影响极大。 # 转为灰度图 img img.convert(L) # 放大2倍改善小尺寸验证码的识别效果 img img.resize((img.width * 2, img.height * 2), Image.LANCZOS) # 增强对比度 img ImageEnhance.Contrast(img).enhance(2.0) # 二值化设定阈值低于阈值变黑高于阈值变白 img img.point(lambda x: 0 if x 140 else 255, 1) # 轻微降噪应用中值滤波去除孤立噪点 img img.filter(ImageFilter.MedianFilter(size3)) return img app.post(/ocr, response_modelOCRResponse) async def ocr_image(file: UploadFile File(...)): 接收上传的验证码图片返回识别文本。 请求方式POST multipart/form-data字段名为file。 start time.time() try: # 读取上传内容 content await file.read() img Image.open(io.BytesIO(content)) # 预处理 processed preprocess_image(img) # Tesseract识别限制字符集为数字大小写字母 custom_config r--psm 7 -c tessedit_char_whitelist0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ text pytesseract.image_to_string(processed, configcustom_config) # 清理结果去掉空白和无关字符 text re.sub(r[^a-zA-Z0-9], , text).strip() cost int((time.time() - start) * 1000) return OCRResponse(texttext, cost_mscost, successTrue) except Exception as e: return OCRResponse(text, cost_ms0, successFalse) app.get(/health) async def health(): return {status: ok} if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)几个关键点的详细解释预处理为什么重要Tesseract对干净、规整的图片识别效果最好。验证码图片往往带有噪点、干扰线、背景纹理如果不做预处理直接扔给Tesseract识别率可能只有三四成。上面这段预处理流程做了四件事灰度化去掉颜色干扰放大两倍让小字符更清晰增强对比度让字符和背景层次更分明二值化把图片变成纯黑白的极大降低Tesseract的干扰。--psm 7是什么含义PSMPage Segmentation Mode是Tesseract的页面分割模式参数7表示“把图片当作单行文本处理”。验证码通常就是一行字符用这个模式最合适。默认的PSM 3全自动页面分割会把验证码当作整页文档处理反而降低准确率且增加耗时。字符白名单tessedit_char_whitelist为什么重要限制识别范围能显著提升准确率。如果你的验证码只有纯数字就把白名单改成0123456789。如果包含字母就加上大小写。这样做的好处是Tesseract不会把0误识别为O、把1误识别为l至少概率大大降低。注意预处理参数阈值140、放大倍数2倍需要根据实际验证码做微调。不同风格、不同尺寸的验证码最优参数不同。我建议先收集20~30张目标验证码图片跑一遍识别统计准确率再针对性调整参数。启动服务python ocr_server.py启动后可以用curl自测curl -X POST http://0.0.0.0:8000/ocr -F filetest_captcha.png返回示例{text:aB3dF9,cost_ms:187,success:true}看到success: true就说明OCR侧已就绪。cost_ms通常在100~300毫秒之间这个速度对测试流程来说完全够用。4.2 识别效果实测数据我在本地准备了三种不同类型的验证码样本各50张统计Tesseract的识别表现验证码类型字符组成样本量正确识别数准确率平均耗时纯数字4位0-9504896%145ms数字小写字母4位0-9a-z504182%192ms数字大小写字母扭曲背景5位0-9a-zA-Z503366%238ms纯数字类型的识别率最高接近96%一旦加入大小写字母和扭曲背景准确率明显下滑。这说明如果你的目标验证码复杂度较高可能需要考虑更高级的OCR方案或者准备一个“识别失败重试”策略详见第7节。4.3 高并发场景下的服务优化单线程跑OCR服务验证码识别速度大约每秒3~5张。如果需要更高吞吐有两个优化方向第一把FastAPI的同步接口改成异步任务队列。上面代码中pytesseract.image_to_string是同步阻塞操作会占用事件循环。可以改为在线程池中执行或者直接使用run_in_executor。第二增加Worker数量。uvicorn启动时加--workers 4就能同时跑4个进程。但要注意Tesseract本身是多线程版本的话进程数乘以线程数不要超过CPU核心数太多否则会因为资源争抢反而变慢。我实测中发现单进程下识别4位数字验证码大约150毫秒4个Worker下能压到40毫秒左右——对BurpSuite的测试流程来说这个性能已经绰绰有余。5. BurpSuite插件开发与配置5.1 插件架构与核心代码逻辑插件部分用JAVA实现主要逻辑代码如下。我基于Montoya API编写结构清晰便于后续扩展。package com.example.burpocr; import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.http.handler.HttpHandler; import burp.api.montoya.http.handler.HttpRequestToBeSent; import burp.api.montoya.http.handler.HttpResponseReceived; import burp.api.montoya.http.handler.RequestToBeSentAction; import burp.api.montoya.http.handler.ResponseReceivedAction; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import java.awt.*; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodyHandlers; import java.util.Base64; import java.util.regex.Matcher; import java.util.regex.Pattern; public class BurpOcrExtension implements BurpExtension, HttpHandler { private static final String OCR_SERVER_URL http://192.168.56.101:8000/ocr; private static final Pattern CAPTCHA_IMG_PATTERN Pattern.compile( img[^]src\([^\]*captcha[^\]*)\[^]*, Pattern.CASE_INSENSITIVE); private static final Pattern CAPTCHA_BASE64_PATTERN Pattern.compile( data:image/(png|jpeg|jpg);base64,([A-Za-z0-9/]), Pattern.CASE_INSENSITIVE); private final HttpClient httpClient HttpClient.newHttpClient(); private volatile String lastOcrResult; private volatile boolean hasCaptchaResponse; Override public void initialize(MontoyaApi api) { api.extension().setName(Local OCR Captcha Solver); api.http().registerHandler(this); api.logging().logToOutput(OCR插件加载成功OCR服务地址: OCR_SERVER_URL); } Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent request) { // 如果是登录请求且验证码字段为空尝试自动填入OCR结果 String body request.bodyToString(); if (request.path().contains(login) hasCaptchaResponse) { if (lastOcrResult ! null !lastOcrResult.isEmpty()) { body body.replaceAll(captcha([^]*), captcha lastOcrResult); HttpRequest modifiedRequest request.withBody(body); hasCaptchaResponse false; return RequestToBeSentAction.continueWith(modifiedRequest); } } return RequestToBeSentAction.continueWith(request); } Override public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived response) { String responseBody response.bodyToString(); // 先尝试提取Base64图片 Matcher base64Matcher CAPTCHA_BASE64_PATTERN.matcher(responseBody); if (base64Matcher.find()) { String imageBase64 base64Matcher.group(2); lastOcrResult sendToOcr(imageBase64); hasCaptchaResponse true; return ResponseReceivedAction.continueWith(response); } // 再尝试提取图片URL Matcher imgMatcher CAPTCHA_IMG_PATTERN.matcher(responseBody); if (imgMatcher.find()) { String imgUrl imgMatcher.group(1); String imageBase64 fetchImageAsBase64(imgUrl); lastOcrResult sendToOcr(imageBase64); hasCaptchaResponse true; } return ResponseReceivedAction.continueWith(response); } private String sendToOcr(String imageBase64) { try { // 把Base64图片数据构造成multipart/form-data请求 String boundary ----BurpOcrBoundary System.currentTimeMillis(); byte[] imageBytes Base64.getDecoder().decode(imageBase64); StringBuilder bodyBuilder new StringBuilder(); bodyBuilder.append(--).append(boundary).append(\r\n); bodyBuilder.append(Content-Disposition: form-data; name\file\; filename\captcha.png\\r\n); bodyBuilder.append(Content-Type: image/png\r\n\r\n); byte[] headBytes bodyBuilder.toString().getBytes(); byte[] tailBytes (\r\n-- boundary --\r\n).getBytes(); // 组装完整请求体 byte[] body new byte[headBytes.length imageBytes.length tailBytes.length]; System.arraycopy(headBytes, 0, body, 0, headBytes.length); System.arraycopy(imageBytes, 0, body, headBytes.length, imageBytes.length); System.arraycopy(tailBytes, 0, body, headBytes.length imageBytes.length, tailBytes.length); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(OCR_SERVER_URL)) .header(Content-Type, multipart/form-data; boundary boundary) .POST(BodyPublishers.ofByteArray(body)) .build(); HttpResponseString response httpClient.send(request, BodyHandlers.ofString()); if (response.statusCode() 200) { // 这里只做简单解析正式使用可引入JSON库 String respText response.body(); Matcher matcher Pattern.compile(\text\:\([^\]*)\).matcher(respText); if (matcher.find()) { return matcher.group(1); } } } catch (Exception e) { e.printStackTrace(); } return ; } private String fetchImageAsBase64(String imgUrl) { // 图片URL转Base64需要先请求目标服务器获取图片 try { HttpRequest request HttpRequest.newBuilder() .uri(URI.create(imgUrl)) .GET() .build(); HttpResponsebyte[] response httpClient.send(request, BodyHandlers.ofByteArray()); return Base64.getEncoder().encodeToString(response.body()); } catch (IOException | InterruptedException e) { e.printStackTrace(); return ; } } }这段代码做了四件事注册HTTP处理器在响应中提取验证码图片支持Base64和URL两种形式把图片发送到OCR服务识别在下一个登录请求中自动填入识别结果。5.2 图片提取逻辑的两种策略Base64与URL实际测试中验证码图片在HTML响应里有两种呈现方式一种是img srcdata:image/png;base64,...直接把图片内容内嵌在HTML里另一种是img src/captcha/captcha.php?idxxx只是一个URL需要再发一次请求拿图片。插件对这两种情况都做了匹配。优先匹配Base64因为省掉一次HTTP请求响应更快如果没匹配到Base64再退回到URL方式额外发起一次请求获取图片。这里有个坑URL获取图片时目标服务器可能要求带Cookie或Referer才能返回图片。我在实现中有这个隐患——如果目标系统对图片接口做了访问控制需要在fetchImageAsBase64方法中加入请求头信息。更通用的做法是直接复用BurpSuite的CookieJar但在Montoya API中操作CookieJar有些繁琐。如果你在真实场景中遇到图片一直识别失败优先排查这一步。5.3 插件编译与加载用Gradle打包插件的流程# 构建配置省略核心是把依赖打入jar包 ./gradlew shadowJar打包完成后在BurpSuite中操作打开BurpSuite进入Extender扩展标签页点击Add添加Extension Type选择Java选择编译好的jar文件点击Next等待加载完成。加载成功的标志是BurpSuite输出面板出现OCR插件加载成功的日志。6. 联调测试把整套流程跑起来6.1 撸清完整测试场景我在本地搭建了一个模拟登录系统来验证整套方案。该系统功能如下登录接口POST /login参数username、password、captcha验证码字段验证码接口GET /captcha/generate返回JSON格式{captcha_base64: ...}登录逻辑用户名密码正确且验证码正确时返回成功否则返回失败这个场景和真实业务系统中的登录逻辑基本一致——验证码独立于用户名密码每次登录请求时单独获取。6.2 手工流程验证先手工验证一下整个链路是否打通。第一步启动OCR服务确认健康检查通过curl http://192.168.56.101:8000/health # 返回 {status:ok}第二步用BurpSuite拦截登录页面请求看到响应中包含了验证码Base64字段。第三步观察BurpSuite输出面板应该能看到插件自动触发了OCR识别并填入请求。如果一切正常登录请求中的captcha字段变成了识别出的验证码文本。我在实际测试中用纯数字4位验证码模拟系统第一次请求就能正确登录耗时约300毫秒OCR识别150ms 网络传输150ms。手动重复10次成功9次失败1次——失败原因是一次识别把某个数字误判了后面会说怎么处理。6.3 结合Intruder实现自动化批量验证手工流程通了之后就可以上自动化了。BurpSuite的Intruder模块可以配合插件工作在Proxy中抓到登录请求右键Send to Intruder设置Payload位置username和password两个字段标记为Payload位置设置Payload类型为Simple list或Brute force最重要的是在Resource Pool中设置合适的线程数——我建议不要太高1~3个线程即可原因下面解释。为什么线程数不能开高因为验证码和登录请求之间存在强时序依赖每个登录请求发出前必须先从服务器取验证码、OCR识别、再填入。如果线程数过多多个线程同时触发OCR识别但lastOcrResult是全局变量会出现A线程识别出的验证码被B线程的请求使用——这样大概率识别错误。实际测试中线程数超过3后误判率急剧上升。如果你的测试场景确实需要高并发需要把插件改造为“每个线程独立维护OCR结果”比如用ThreadLocal变量但代码复杂度会上升不少。对大多数授权测试场景来说单线程或者低并发就够用了。6.4 目标系统验证码独立于登录接口的常见场景还有一种常见场景验证码图片并不是登录页面返回的而是独立接口返回的。例如登录页先调用GET /captcha/generate获取验证码把验证码ID和图片都放到Cookie或前端隐藏字段中用户提交登录时同时提交验证码文本和验证码ID。这种情况下上面插件的“从登录响应中提取验证码”逻辑就不适用了。需要调整思路在并发测试中先发一个单独的请求获取验证码OCR识别后再把结果和验证码ID拼接到登录请求中。这个场景的插件改造方案是把“获取验证码”和“提交登录”拆成两个独立步骤在测试开始时先执行一次验证码获取请求把OCR结果保存起来后续的登录请求循环复用同一个验证码——实际测试中这种方法能用但严格来说如果目标系统对验证码做了“一次性”校验用过即废这种方案的准确率就会大打折扣。针对这类情况建议在测试策略上考虑降低并发或延长任务调度间隔。7. 常见问题与排查技巧实录7.1 OCR识别失败或误判率高这是整套方案里最常遇到的问题。排查顺序如下先检查验证码图片本身质量。用curl手动下载一张验证码肉眼观察字符是否清晰干扰线是否严重背景色是否与字符色接近如果肉眼都难以辨认OCR自然也不行。检查预处理参数是否匹配。不同尺寸、不同颜色的验证码需要不同的二值化阈值。我建议写个小脚本批量测试不同阈值下的识别率选最优的。关键参数是point函数里的阈值140以及放大倍数2。检查Tesseract版本和语言包。sudo apt install tesseract-ocr默认只装英文包如果你遇到中文验证码需要额外安装tesseract-ocr-chi-sim。另外新版本Tesseract5.x比旧版本4.x的准确率有明显提升。确认PSM模式是否合适。字符间距大的验证码用--psm 7没问题如果验证码字符间有空格或断连可以考虑--psm 8单字符模式再拼接。7.2 BurpSuite插件未触发OCR识别检查插件是否加载成功BurpSuite输出面板是否有日志。确认响应中确实包含验证码图片。在Proxy的HTTP History里找到登录页面响应搜索captcha或base64关键词确认图片数据存在。如果验证码图片是JS动态生成的比如前端canvas绘制插件无法从纯HTML中提取。这种情况需要额外的JS执行能力或者把验证码URL直接发给OCR服务识别。7.3 OCR结果与验证码实际值不一致一定要区分误判和识别失败。误判是“识别出文本但文本本身是错的”这种在复杂验证码中最难防。我的经验是两个策略配合使用一是“多次识别取多数”。对同一张验证码图片调用OCR服务3次每次预处理参数稍作调整取出现次数最多的结果。这能把准确率提升好几个百分点代价是耗时增加。二是“识别置信度过滤”。Tesseract可以输出置信度低于某个阈值比如60就判定为识别失败让插件放弃本次识别重新获取验证码再来一次。这需要修改OCR服务的返回结构增加置信度字段。7.4 常见问题速查表问题现象可能原因排查方法OCR服务启动失败端口被占用换端口或lsof -i:8000查看占用进程OCR返回successfalse上传的文件不是一个有效图片检查图片Base64解码逻辑登录请求一直提示验证码错误识别的验证码有误单独下载图片测试手动识别效果BurpSuite日志没有任何输出插件未加载 or 响应中无图片数据检查Extension加载状态、检查响应BodyIntruder高并发时准确率骤降全局变量竞争改用单线程或ThreadLocal改造OCR识别速度很慢1秒Tesseract处理复杂图片优化预处理参数启用--psm 78. 加固视角这套方案对防护方的启示聊完攻击测试侧站在防御视角来看这套组合拳能顺利跑通恰恰暴露了很多系统在验证码设计上的薄弱环节。这也是安全测试的真正价值——不是展示工具多强大而是帮防护方找到短板。首先验证码的设计强度需要匹配业务重要性。登录接口是风控的第一道闸门如果验证码只是4位纯数字、无扭曲、无干扰线那么OCR识别率接近96%和人工识别没什么差别——这道防线等于形同虚设。建议至少使用6位以上、包含大小写字母和数字、带干扰线或扭曲效果的验证码让OCR准确率降到50%以下。其次验证码必须和登录接口做绑定校验。很多系统的漏洞在于验证码是全局公共的任何登录请求只要提交任意一个有效验证码就能通过。正确做法是验证码与SessionID绑定且一次性使用——用过的验证码立刻失效同一验证码无法重复提交。再次服务端必须限制登录失败次数。暴力破解的关键在于可以无限次尝试。如果系统限制单IP或单账号在一段时间内的失败次数例如5分钟最多5次哪怕验证码被OCR攻破攻击者也很难完成批量破解。额外说一点即使验证码存在缺陷如果账号锁定策略做得好——连续输错N次密码就锁定账号并触发人工审核——攻击者的成本会成倍上升。这套本地OCR插件的组合拳本质上是一个授权的安全评估工具。它的价值不在于“绕过验证码”本身而在于能客观地评估验证码安全强度、登录接口防护水平最终推动安全加固落地。我在做完一轮测试后把完整报告提交给目标系统的开发团队他们立刻针对OCR识别率高的几个问题做了修正——验证码升级为扭曲干扰型、登录失败锁定策略从无到有。这就是安全测试最理想的结果。如果你在配置过程中遇到其他问题可以先从我的排查顺序入手定位。实在不行把问题分解开来验证先用curl测OCR服务再用BurpSuite手工流程测试最后才上Intruder自动化——哪一步出问题就修哪一步不要一上来就跑完整链路。