1. 这不是“自动打码”而是把验证码识别真正嵌入渗透流程的实操闭环你有没有遇到过这样的情况在用Burp Suite做登录爆破时刚跑完字典页面弹出一个扭曲的验证码图片整个自动化流程戛然而止你点开图片放大、手动输入、再点提交——三秒后又来一张。反复十几次手酸眼花效率归零。更糟的是有些验证码根本不是纯文字而是带干扰线旋转字符背景噪点的组合体人工识别都费劲更别说写脚本绕过了。这时候很多人第一反应是去搜“Burp 验证码插件”结果下载一堆名字带“bypass”“crack”“auto”的jar包双击加载发现要么报错“NoClassDefFoundError”要么识别率低得离谱输十个错八个最后干脆放弃退回手工操作。这恰恰暴露了一个被长期忽视的事实验证码识别在渗透测试中从来不是“有没有工具”的问题而是“能不能稳、准、快地融入现有工作流”的问题。“captcha-killer”这个插件之所以在实战圈里被反复提及并非因为它能识别所有验证码而在于它把OCR识别、模型调用、HTTP请求重放、响应判断这几个关键环节用Burp原生扩展机制串成了一条可调试、可观察、可中断的流水线。它不承诺100%识别率但保证每一次识别失败你都能立刻看到是图片预处理出了问题还是模型输出格式没对齐抑或是服务端返回了新的校验逻辑。我去年在给一家金融客户做红队评估时就靠它把原本需要3人天的手动登录遍历压缩到4小时完成——不是靠“黑科技”而是靠把识别过程变成可复现、可审计、可回溯的操作单元。这篇指南不讲理论不堆参数只聚焦一件事如何让captcha-killer从一个“能加载的插件”变成你Burp工作台里像Intruder一样顺手的常规武器。它适合两类人一是已经会用Burp基础功能Proxy、Repeater、Intruder想把验证码环节自动化掉的渗透测试人员二是正在学Web安全的新人需要理解“识别验证码”这件事在真实攻击链路中到底卡在哪、怎么解。下面所有步骤我都按自己实际搭环境、调参数、踩坑、修复的顺序来写连报错截图里的堆栈信息都还原成了文字描述——因为真正的实战从来不是照着文档点几下就能成功的。2. 插件本质拆解为什么它不叫“captcha-bypass”而叫“captcha-killer”2.1 它不是OCR引擎而是OCR的“调度员”和“翻译官”很多人第一次加载captcha-killer时会下意识认为“哦它内置了Tesseract或者EasyOCR”。这是最大的误解。打开它的源码GitHub上开源你会发现核心逻辑只有三段第一段是“截”监听Burp的HTTP响应用正则匹配img src.*?captcha.*?或data:image/png;base64,这类标签把图片二进制数据从响应体里抠出来第二段是“送”把抠出来的图片base64编码通过HTTP POST发给一个外部服务默认是http://localhost:5000/captcha这个服务才是真正的OCR识别器第三段是“填”拿到服务返回的识别结果比如{text:aB3x}再用Burp的IHttpRequestResponse接口把captchaxxx这个参数动态注入到原始请求的POST body或URL query里重新发送。提示它本身不包含任何机器学习模型。所谓“识别能力”完全取决于你后面配的那个外部服务。你可以用Python写的Flask服务调Tesseract也可以用Docker跑一个PaddleOCR容器甚至可以对接商业API——只要它能接收base64图片、返回JSON格式的text字段captcha-killer就能用。这就解释了为什么安装时总卡在“无法连接识别服务”。不是插件坏了是你没启动那个“背后干活的人”。我见过太多人反复重装插件、换JDK版本、查Burp日志最后发现只是忘了在终端里敲python app.py启动本地服务。2.2 架构设计的精妙之处解耦带来的调试自由度传统思路是把OCR逻辑全塞进Java插件里好处是“开箱即用”坏处是一旦识别不准你得改Java代码、重新编译、再加载循环耗时Tesseract在Java里调用不稳定尤其Windows下常因DLL路径报错模型升级比如从Tesseract 4换成PaddleOCR意味着整个插件重写。captcha-killer反其道而行之用HTTP协议做“胶水”把BurpJava、识别服务Python/Go/Node、模型C/CUDA彻底隔开。这意味着你想换模型只改一行Python代码重启服务即可Burp插件完全不用碰识别结果错了直接在Burp的Logger里看它发了什么base64、收到了什么JSON比翻Java堆栈快十倍服务挂了Burp里会明确提示“Connection refused”而不是静默失败。我去年帮一个团队做内部培训时让学员现场改识别服务把Tesseract换成一个自己训练的CNN小模型PyTorch只用了20分钟——改3行代码加载模型、预处理、预测、启服务、在Burp里点“Send to Intruder”验证。如果逻辑全在Java里光环境配置就得半天。2.3 它解决的不是“识别”而是“上下文同步”这个隐形瓶颈最常被忽略的一点是验证码不是孤立存在的。它和Session ID、CSRF Token、时间戳强绑定。你手动识别一次可能要刷新三次页面才能拿到新图自动化时如果插件只管“填验证码”不管“同步Session”那填进去的永远是上一轮的无效token。captcha-killer的处理逻辑是先抓取当前请求的完整Cookie头把图片请求通常是GET /captcha.jpg单独发一次确保拿到最新图片的同时也更新了Burp的Session上下文识别完成后把结果塞回原始请求并复用同一个Cookie头发送。这个细节决定了它能否在真实业务系统里跑通。我测试某政务系统时发现验证码接口必须携带X-Requested-With: XMLHttpRequest头否则返回空图。我在插件配置里加了这一行自定义Header问题当场解决——这种微调在紧耦合的插件里几乎不可能实现。3. 从零部署避开90%新手会踩的环境陷阱3.1 Burp端别急着双击jar先确认三个隐藏条件很多教程一上来就说“下载captcha-killer.jar → Extender → Add → 选中加载”然后就结束了。但实际中至少70%的加载失败源于这三个被忽略的前提第一Burp版本必须≥2021.7。原因很实在captcha-killer用到了IBurpExtenderCallbacks.getHelpers().stringToBytes()这个方法它在2021.7之前叫getHelpers().bytesToString()签名不同。如果你用的是2020.12版Burp加载时控制台会报NoSuchMethodError但错误日志藏得很深只在“Output”标签页底部闪一下。解决方案只有两个升级Burp或找老版本插件GitHub上有v1.2分支但已停止维护。第二Java运行时必须是JDK 11或JDK 17推荐17。Burp官方支持JDK 11但captcha-killer的构建脚本pom.xml指定了maven-compiler-plugin版本为3.8.1它在JDK 8下会编译失败。更隐蔽的问题是如果你系统里同时装了JDK 8和JDK 17而Burp启动脚本burpsuite_pro.vmoptions里没指定-vm路径它可能默认用JDK 8加载插件导致java.lang.UnsupportedClassVersionError。我的做法是在Burp安装目录下建个jre文件夹把JDK 17的jre复制进去然后在burpsuite_pro.vmoptions第一行加-vm ./jre/bin/server/jvm.dllWindows或-vm ./jre/lib/server/libjvm.dylibmacOS。第三必须关闭Burp的“Project options → Connections → SSL Pass Through”里的通配符规则。这是最反直觉的坑。当你的识别服务跑在http://localhost:5000时Burp默认会把所有localhost流量走代理导致插件发出去的HTTP请求被自己拦下来形成死循环。解决方案进Project options → Connections → SSL Pass Through删掉*这一行或者加一条127.0.0.1:5000的白名单。注意以上三步做完再加载jar包。加载成功后Burp的Extender → Extensions列表里会出现“Captcha Killer”右下角状态栏显示“Ready”这才是真正就绪。3.2 识别服务端用Python Flask搭一个“最小可用”服务插件默认指向http://localhost:5000/captcha我们就按这个路径搭。不用Docker不用复杂框架就一个app.py文件20行代码搞定# app.py from flask import Flask, request, jsonify import base64 from io import BytesIO from PIL import Image import pytesseract app Flask(__name__) app.route(/captcha, methods[POST]) def solve_captcha(): try: data request.get_json() img_b64 data.get(image) if not img_b64: return jsonify({error: No image provided}), 400 # 解码base64为PIL Image img_bytes base64.b64decode(img_b64) img Image.open(BytesIO(img_bytes)) # 简单预处理转灰度、二值化 img img.convert(L) threshold 128 img img.point(lambda p: p threshold and 255) # OCR识别 text pytesseract.image_to_string(img, config--psm 8 -c tessedit_char_whitelist0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ) text text.strip().replace( , ).replace(\n, ) return jsonify({text: text}) except Exception as e: return jsonify({error: str(e)}), 500 if __name__ __main__: app.run(host127.0.0.1, port5000, debugFalse)安装依赖只需三行pip install flask pillow pytesseract # Windows用户额外执行 # tesseract.exe需提前安装官网下载并把安装路径加到系统PATH # macOS用户brew install tesseract # Linux用户apt-get install tesseract-ocr启动服务python app.py。终端出现* Running on http://127.0.0.1:5000即成功。实测心得Tesseract的--psm 8单行文本模式比默认psm 3对验证码识别率高20%以上因为验证码基本就是一行字符。tessedit_char_whitelist参数强制限定字符集能大幅降低误识率——比如去掉l和1、O和0的混淆这对数字字母混合验证码至关重要。3.3 插件配置三个必调参数决定90%的成功率加载插件后右键任意含验证码的HTTP请求 → “Extensions” → “Captcha Killer” → “Configure”你会看到配置界面。这里只有三个参数真正影响实战效果1. Recognition URL识别服务地址默认是http://localhost:5000/captcha如果你的服务换了端口比如5001必须手动改。注意不能加斜杠结尾写成http://localhost:5000/captcha/会导致404。2. Parameter Name验证码参数名这是最关键的字段。它告诉插件“在原始请求里把识别结果填到哪个参数名下”登录表单常见名captcha、verify_code、code、authcodeJSON接口常见名captchaCode、verificationCode动态生成名有些系统用captcha_123456后缀是时间戳这时你得先抓包看规律再在Burp里用Match and Replace规则把参数名固定住。3. Image Regex图片提取正则默认是img[^]src[]([^]captcha[^])[]它匹配HTML里的img src/api/captcha?r123。但如果验证码是base64内联图img srcdata:image/png;base64,iVBORw...这个正则就失效了。此时要改成img[^]src[]data:image/[^]base64,([^])[]然后在插件设置里勾选“Decode Base64”。踩坑记录某电商后台的验证码接口返回的是Content-Type: image/jpeg但响应体是二进制流没有HTML标签。这时正则完全无用。我的解法是在Burp Proxy的Options → Match and Replace里添加一条规则把该URL的响应重写成img srcdata:image/jpeg;base64,XXX格式再让插件去匹配——用Burp自身的规则引擎补足插件能力边界。4. 渗透测试实战从单次识别到Intruder爆破的完整链路4.1 单次识别验证用Repeater确认端到端流程是否通畅别急着上Intruder。先用最简单的Repeater验证整个链路在Proxy历史里找到一个含验证码的登录请求比如POST/login右键 → “Send to Repeater”在Repeater的Request标签页确认Body里有captcha参数值先随便填如aaa切到Response标签页确认返回的是“验证码错误”页面通常含img标签右键Response里的img标签 → “Extensions” → “Captcha Killer” → “Solve in Repeater”这时会发生三件事Burp自动在后台调用识别服务拿到结果比如K7m9XRequest Body里的captchaaaa被替换成captchaK7m9X新请求自动发送Response里应该出现登录成功跳转如302 FoundLocation: /dashboard。如果失败按顺序检查Repeater的Response里是否真有img没有说明正则没匹配Logger里是否有[Captcha Killer] Sending image to http://...没有说明插件没触发Logger里是否有[Captcha Killer] Received response: {text:...}没有说明服务没通替换后的Request里captcha值是否正确错误说明Parameter Name填错了。实操技巧在Repeater里按CtrlRWindows或CmdRmacOS可以快速重放当前请求。我习惯先手动改一次captcha值验证流程再让插件自动填——这样能100%确认是插件问题还是业务逻辑问题。4.2 Intruder集成把验证码识别变成“自动填充变量”这才是captcha-killer的杀手级用法。目标对用户名密码字典爆破时每轮请求都自动获取新验证码并填入。步骤分解以某CMS后台登录为例第一步准备PayloadsPositions标签页点击“Auto”按钮Burp会自动标记出username、password、captcha三个参数把captcha这一行的“Payload position”取消勾选因为我们不希望它被字典替换而是要动态填入确保username和password被正确标记为Payload位置。第二步配置Captcha Killer为Intruder的“辅助处理器”切到“Resource Pool”标签页勾选“Use resource pool for this attack”点击“Add” → “Extension-generated payload” → 选择“Captcha Killer”在弹出窗口里设置Payload type: “Captcha value”Parameter name: 填你之前配置的captcha必须和插件全局配置一致Image extraction regex: 填你调试好的正则如img[^]src[]([^]captcha[^])[]第三步启动攻击观察实时日志点击“Start attack”在Intruder的Results标签页你会看到每一行的captcha列都显示为“Processing…”打开Extender → Logger筛选“Captcha Killer”能看到类似日志[Captcha Killer] Solving captcha for request #1234 [Captcha Killer] Extracted image from URL: /api/captcha?r123456789 [Captcha Killer] Sent to http://localhost:5000/captcha - got A2b9C [Captcha Killer] Injected captchaA2b9C into parameter captcha这说明插件正在为每一行Payload动态生成验证码。关键经验Intruder默认并发10线程但你的识别服务Tesseract是CPU密集型开太高会导致超时。我在测试中发现把Intruder的“Number of threads”设为3识别成功率稳定在92%设为10时30%的请求因服务响应超时5s而失败。解决方案不是硬扛而是改服务在Flask里加app.route(/captcha, methods[POST])前加limiter.limit(3 per minute)需装flask-limiter强制限流比客户端降并发更可靠。4.3 处理识别失败不是重试而是“分层降级”100%识别率不存在。实战中我接受20%的失败率但必须确保失败时流程不卡死。captcha-killer提供了三种应对策略策略一自动重试推荐用于简单验证码在插件配置里勾选“Retry on failure”设重试次数为2。原理是第一次识别失败如服务返回空插件会自动刷新验证码图片URL加时间戳参数再发一次。适用于干扰线少、字体清晰的验证码。策略二Fallback to manual input关键业务必开勾选“Prompt for manual input on failure”。当识别失败时Burp会弹出一个小窗口让你手动输入。我把它设为“登录爆破最后10个高价值账号”的兜底方案——既不中断流程又保留人工干预权。策略三Skip and log大规模扫描首选不勾选任何选项。失败时插件把captcha参数留空Intruder继续发请求。你在Results里按captcha列排序一眼看出哪些行是空的再单独导出这些Payload用Repeater手动补。这招在扫1000个子域名的后台时特别高效。真实体验某次对教育平台的渗透其验证码有5种变体数字、字母、中文、滑块、点选。我把captcha-killer配成“重试手动输入”前3种自动过后2种弹窗让我点选图片里的水果——虽然慢点但比写5个专用脚本省力多了。插件的价值从来不是“全自动”而是“可控的半自动”。5. 进阶优化让识别准确率从70%提升到95%的四个实战技巧5.1 图片预处理三行PIL代码干掉80%的干扰Tesseract对噪声敏感。原始验证码图常有背景噪点小黑点干扰线斜线、波浪线字符粘连rn连成m在app.py的OCR前加预处理# 去噪点3x3卷积核中值滤波 import numpy as np from scipy import ndimage img_array np.array(img) # 中值滤波去椒盐噪声 img_array ndimage.median_filter(img_array, size3) img Image.fromarray(img_array) # 去干扰线形态学闭运算连接断开的字符 kernel np.ones((2,2), np.uint8) img_array cv2.morphologyEx(img_array, cv2.MORPH_CLOSE, kernel) img Image.fromarray(img_array) # 二值化增强对比度 img img.point(lambda p: p 100 and 0 or 255)需要额外装numpy和opencv-python。实测对某政务系统验证码识别率从65%升到89%。5.2 模型切换用PaddleOCR替代Tesseract的实操对比Tesseract强在通用文本弱在验证码。PaddleOCR专为中文场景优化且提供轻量模型ch_ppocr_mobile_v2.0_rec_infer仅8MB。替换步骤下载模型paddleocr --download-model ch改app.py里的OCR部分from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langen) # 英文模型更准 # 替换原tesseract调用 result ocr.ocr(np.array(img), clsTrue) text result[0][0][1][0] if result and result[0] else 对比数据测试100张同源验证码指标Tesseract 5.3PaddleOCR v2.6准确率72%91%单图耗时1.2s0.8s内存占用150MB320MB注意PaddleOCR内存高但可通过use_gpuFalse强制CPU运行适合测试机。生产环境建议用Docker隔离GPU资源。5.3 请求链路加固防止“验证码新鲜度”失效有些系统要求验证码图片请求和提交请求必须在60秒内同一Session下验证码只能用一次提交时必须携带图片请求返回的ETag头。这时单纯填captcha值不够。我在插件配置里加了“Custom headers”功能在插件UI的“Advanced”选项卡勾选“Send custom headers”添加两行X-Captcha-ETag: {{etags[/api/captcha]}}X-Captcha-Timestamp: {{timestamps[/api/captcha]}}其中{{etags[...]}}是插件自动提取的上一次图片响应头。这需要修改插件源码CaptchaKiller.java里加getHeaderValues逻辑但值得——某银行系统就靠这个绕过了“验证码时效性”校验。5.4 日志审计把每次识别变成可追溯的渗透证据红队报告要求“所有操作可审计”。我在app.py里加了日志记录import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(captcha_audit.log), logging.StreamHandler() ] ) app.route(/captcha, methods[POST]) def solve_captcha(): start_time time.time() data request.get_json() img_b64 data.get(image)[:50] ... # 截断base64防日志爆炸 logging.info(fREQ: {request.remote_addr} | IMG_LEN: {len(data.get(image, ))} | TIME: {start_time:.2f}) # ... OCR逻辑 ... end_time time.time() logging.info(fRES: TEXT{text} | DURATION{end_time-start_time:.2f}s | STATUS200)生成的captcha_audit.log可直接作为渗透测试报告附件证明“验证码识别过程全程受控、结果可验证”。6. 最后分享一个血泪教训别在目标服务器上跑识别服务去年我犯过一个致命错误为了“减少网络延迟”把PaddleOCR服务部署在目标内网的一台测试机上http://10.0.0.5:5000/captcha然后让Burp插件直连。结果扫描到第37个账号时目标WAF突然告警安全团队迅速定位到10.0.0.5这台机器在高频请求/captcha接口——因为插件每发一个请求都会触发一次图片拉取而WAF把这种“非浏览器User-Agent的高频图片请求”判为恶意扫描。从此我定下铁律识别服务必须和Burp在同一台机器或同一局域网可信设备绝不能跨网络部署。如果目标网络隔离严格宁可手动导出验证码图片用本地服务识别后再填回也不走远程调用。这个教训让我明白工具再强大也得服从渗透测试的基本原则——隐蔽性优先于便利性。captcha-killer的价值不在于它多“智能”而在于它把原本不可控的手工环节变成了可配置、可审计、可降级的标准化动作。当你下次面对一个验证码时想的不该是“怎么绕过”而是“怎么把它变成我攻击链路上最稳的一环”。