1. 项目概述:当安全测试遇上图片验证码
在渗透测试和Web应用安全评估的日常工作中,图片验证码(CAPTCHA)常常是自动化工具面前的一道“叹息之墙”。无论是进行暴力破解、撞库攻击测试,还是自动化爬取敏感数据,一个设计得当的验证码就能让脚本瞬间哑火。传统的应对方式要么是手动识别,效率低下且枯燥;要么是寻找验证码逻辑漏洞,但这并非每次都能奏效。于是,一个将开源OCR(光学字符识别)技术与Burp Suite这款安全测试“瑞士军刀”相结合的想法便应运而生。这个实战项目的核心,就是打造一个能够集成到Burp Suite工作流中的插件,让它能自动识别请求中的图片验证码,并将识别结果填充回请求中,从而实现测试流程的半自动化甚至全自动化。
这不仅仅是写几行代码调用一个API那么简单。它涉及到对HTTP请求/响应流的深度理解、对Burp Extender API的熟练运用、对OCR引擎的调优,以及一套健壮的错误处理和重试机制。整个过程就像是为Burp Suite安装了一个“智能眼睛”和“机械手”,让它能看懂图片里的文字,并替我们完成重复的填充动作。无论是数字、字母混合的简单验证码,还是一些经过轻微干扰的复杂类型,通过合理的预处理和模型选择,我们都有可能实现较高的识别成功率,从而将安全测试人员从重复劳动中解放出来,专注于更复杂的逻辑漏洞挖掘。
2. 核心思路与技术选型解析
2.1 为什么选择“Burp插件 + 开源OCR”这条路径?
面对验证码,通常有几种思路:一是利用第三方打码平台API,优点是准确率高、省心,但会产生持续费用,且在某些内网或保密要求高的测试场景中,将数据发送到外部平台存在安全合规风险。二是训练专用的深度学习模型,这能获得针对特定验证码的最佳效果,但需要大量的标注数据、较强的算法知识和训练成本,对于快速响应、临时性的测试任务来说过于笨重。
因此,“Burp插件 + 开源OCR”的组合成了一种平衡性极强的方案。首先,Burp Suite是安全测试的事实标准,其强大的代理、重放、扫描功能与可扩展的插件体系,为集成验证码识别提供了完美的舞台。插件可以直接操作经过Burp的HTTP流量,无缝嵌入现有工作流。其次,选择开源OCR引擎(如Tesseract)意味着零成本、可离线运行、完全自主可控。虽然其开箱即用的准确率可能不及商业API或专用模型,但通过图像预处理、参数调优和字典限制,我们完全可以在特定类型的验证码上达到实用级的识别率。这条路径的核心优势在于自主、可控、可定制、零持续成本,非常适合安全研究人员和渗透测试工程师。
2.2 核心组件与工作流程设计
整个系统的架构可以清晰地分为三个层次:流量拦截层、图像处理与识别层、结果回填层。
流量拦截层 (Burp Plugin):这是整个系统的“大脑”和“调度中心”。我们需要编写一个Burp插件(通常使用Java或Python,通过Jython或JPython集成)。该插件需要实现
IHttpListener接口,监听经过Burp的每一个HTTP请求和响应。其核心职责是:- 检测验证码请求:通过URL特征、响应Content-Type(
image/jpeg,image/png)或HTML中的特定标签,判断当前响应是否包含了一个需要识别的验证码图片。 - 提取图像数据:从HTTP响应体中提取出二进制的图片数据。
- 调用识别层:将图片数据传递给OCR识别引擎。
- 回填识别结果:获取OCR返回的文本后,根据规则(如查找请求中的
captcha、code等参数名)定位到需要修改的请求参数,并用识别结果替换其值,最后将修改后的请求发送出去或提供给测试者使用。
- 检测验证码请求:通过URL特征、响应Content-Type(
图像处理与识别层 (OCR Engine):这是系统的“眼睛”。我们选用Tesseract OCR作为核心引擎。但原始验证码图片往往带有噪声、干扰线、扭曲、背景色块等,直接丢给Tesseract识别率会很低。因此,一个关键的预处理管道必不可少:
- 二值化:将彩色或灰度图转为黑白图,区分前景(文字)和背景。常用方法有全局阈值、自适应阈值(如OpenCV的
cv2.adaptiveThreshold)。 - 降噪:去除孤立的像素点、细小的干扰线。可以使用中值滤波、形态学操作(如开运算、闭运算)。
- 字符分割(可选):对于字符粘连严重的验证码,可能需要尝试投影法、连通域分析等方法进行分割,再分别识别,但这会大大增加复杂度。通常优先调优Tesseract的
--psm(页面分割模式)参数来应对。 - 调用Tesseract:通过命令行或
pytesseract等库调用Tesseract,并指定语言包(如eng)、PSM模式(对于单行验证码,--psm 7或--psm 8很常用)和白名单(如--oem 3 --psm 8 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ)。
- 二值化:将彩色或灰度图转为黑白图,区分前景(文字)和背景。常用方法有全局阈值、自适应阈值(如OpenCV的
结果回填层 (Integration Logic):这是系统的“手”。识别出文本后,需要智能地填充到后续的请求中。插件需要维护一个简单的上下文,记住哪个验证码图片对应哪个即将发出的请求(通常是下一个包含特定参数的POST请求)。回填策略可以是自动的(插件自动修改并转发请求),也可以是手动的(将识别结果展示在Burp界面,由测试者确认后手动粘贴)。
2.3 技术栈选型详述
Burp插件开发:
- 语言:首选Java。因为Burp Suite本身是Java编写的,使用Java开发插件兼容性最好,性能最优,可以直接使用Burp提供的完整API。另一种选择是Python,通过Jython桥接,虽然灵活但在处理复杂Burp API和性能上可能稍逊一筹。
- API:关键接口包括
IHttpListener(监听HTTP流量)、ITab(创建自定义UI标签页)、IContextMenuFactory(创建右键菜单)等。我们需要重点研究IHttpListener的processHttpMessage方法,它能捕获所有经过代理的请求和响应。 - 开发环境:建议使用IntelliJ IDEA或Eclipse,配置好Burp Extender API的JAR包作为依赖。
OCR引擎:
- Tesseract OCR:开源OCR的标杆,由Google维护。识别英文和数字效果较好,对中文等语言需要额外训练数据。它支持多种输出格式,并且可以通过命令行参数进行精细控制。
- 预处理库:OpenCV (Open Source Computer Vision Library)是不二之选。它提供了极其丰富的图像处理函数,从简单的裁剪、缩放、到复杂的滤波、形态学操作、轮廓检测,足以应对绝大多数验证码的预处理需求。通常使用其Python接口(
cv2)或Java接口进行开发。
部署与集成:
- 插件需要将Tesseract引擎和OpenCV库打包或依赖其系统安装。一种可靠的方式是要求用户在系统路径中安装好Tesseract,插件通过调用命令行来使用它。OpenCV库则可以打包进插件的JAR文件中(对于Java),或确保Python环境已安装。
注意:此方案主要针对安全性较弱的验证码。它利用了验证码本身可能存在的识别漏洞。对于使用了强干扰、动态行为验证(如滑动拼图、点选文字)、深度学习模型的现代验证码(如极验、腾讯验证码),此方案成功率极低。它的适用场景是内部系统、老旧系统或开发者自制的简单验证码的辅助测试。
3. 实战环境搭建与核心代码实现
3.1 基础环境准备
工欲善其事,必先利其器。首先,我们需要在本地搭建好所有必要的开发与运行环境。
安装Burp Suite Professional:确保你拥有Burp Suite专业版,社区版对插件开发的支持有限。从PortSwigger官网下载并激活。
安装Java开发环境 (JDK):建议安装JDK 8或11(与Burp兼容性较好)。配置好
JAVA_HOME环境变量。安装Tesseract OCR:
- Windows:从GitHub上的UB-Mannheim项目下载Tesseract安装程序。安装时记得勾选“将Tesseract添加到系统路径”。同时,下载所需的语言数据包(如
eng.traineddata),放入Tesseract安装目录的tessdata文件夹中。 - macOS:使用Homebrew安装最为简便:
brew install tesseract。 - Linux:使用包管理器安装,例如Ubuntu/Debian:
sudo apt install tesseract-ocr,以及sudo apt install libtesseract-dev。 - 安装后,在命令行输入
tesseract --version,确认安装成功。
- Windows:从GitHub上的UB-Mannheim项目下载Tesseract安装程序。安装时记得勾选“将Tesseract添加到系统路径”。同时,下载所需的语言数据包(如
安装OpenCV:
- 如果使用Java开发,需要将OpenCV的Java库(
.jar和.dll/.so/.dylib)添加到项目依赖中。 - 如果使用Python(通过Jython),则使用pip安装:
pip install opencv-python-headless(headless版本适用于无GUI的服务器环境)。
- 如果使用Java开发,需要将OpenCV的Java库(
配置开发工具:以IntelliJ IDEA为例,创建一个新的Java项目。将Burp Suite启动时加载的
burpsuite_pro.jar(位于Burp安装目录)作为库文件添加到项目的依赖中。这个JAR包包含了所有Burp Extender API。
3.2 Burp插件骨架与HTTP监听器实现
首先,我们创建一个基本的Burp插件类,并实现核心的IHttpListener接口。
package com.example.burp.ocrplugin; import burp.*; import java.awt.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; // 必须实现IBurpExtender接口,这是所有Burp插件的入口 public class BurpExtender implements IBurpExtender, IHttpListener, ITab { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private PrintWriter stdout; private PrintWriter stderr; // 自定义UI组件(后续可扩展) private JPanel mainPanel; private JTextArea logArea; // 用于存储上下文信息,如图片ID与对应请求的关系 private String lastCaptchaImageHash; private IHttpRequestResponse lastRequestRequiringCaptcha; @Override public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) { this.callbacks = callbacks; this.helpers = callbacks.getHelpers(); this.stdout = new PrintWriter(callbacks.getStdout(), true); this.stderr = new PrintWriter(callbacks.getStderr(), true); // 设置插件名称 callbacks.setExtensionName("OCR Captcha Cracker"); // 注册HTTP监听器 callbacks.registerHttpListener(this); // 初始化UI(简化版) SwingUtilities.invokeLater(new Runnable() { @Override public void run() { initializeUI(); callbacks.addSuiteTab(BurpExtender.this); } }); stdout.println("OCR Captcha Cracker Plugin Loaded Successfully!"); } private void initializeUI() { mainPanel = new JPanel(new BorderLayout()); logArea = new JTextArea(20, 60); logArea.setEditable(false); JScrollPane scrollPane = new JScrollPane(logArea); mainPanel.add(scrollPane, BorderLayout.CENTER); // 可以添加按钮、配置面板等 } @Override public Component getUiComponent() { return mainPanel; } // 核心方法:处理每一个经过Burp的HTTP消息 @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { // 只处理从Proxy、Repeater、Intruder等工具来的消息 if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY && toolFlag != IBurpExtenderCallbacks.TOOL_REPEATER && toolFlag != IBurpExtenderCallbacks.TOOL_INTRUDER) { return; } if (!messageIsRequest) { // 这是一个HTTP响应 processHttpResponse(toolFlag, messageInfo); } else { // 这是一个HTTP请求(在发送前) processHttpRequest(toolFlag, messageInfo); } } private void processHttpResponse(int toolFlag, IHttpRequestResponse messageInfo) { IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse()); List<String> headers = responseInfo.getHeaders(); // 检查响应头,判断是否为图片 for (String header : headers) { if (header.toLowerCase().startsWith("content-type: image/")) { // 发现图片响应!记录下这个响应,并尝试识别 log("发现图片响应,URL: " + helpers.analyzeRequest(messageInfo).getUrl().toString()); handleCaptchaImage(messageInfo); break; } } // 也可以检查响应体内容或URL路径中是否包含`captcha`, `code`, `verify`等关键词 } private void processHttpRequest(int toolFlag, IHttpRequestResponse messageInfo) { // 检查即将发出的请求,看它是否需要验证码参数 // 这里是一个简单的示例:查找请求参数中是否有`captcha`或`verificationCode` IRequestInfo requestInfo = helpers.analyzeRequest(messageInfo); List<IParameter> params = requestInfo.getParameters(); for (IParameter param : params) { if (param.getName().toLowerCase().contains("captcha") || param.getName().toLowerCase().contains("verify") || param.getName().toLowerCase().contains("code")) { log("发现需要验证码的请求,参数名: " + param.getName()); // 将当前请求标记为“待填充验证码的请求” lastRequestRequiringCaptcha = messageInfo; // 可以在这里尝试自动填充之前识别到的验证码 autoFillCaptchaIfPossible(messageInfo, param); break; } } } private void log(String message) { logArea.append(message + "\n"); stdout.println("[OCR Plugin] " + message); } // ... 后续将实现 handleCaptchaImage 和 autoFillCaptchaIfPossible 方法 }这段代码搭建了插件的基本框架。它注册了HTTP监听器,能够拦截所有请求和响应。当发现图片类型的响应时,会触发handleCaptchaImage方法;当发现包含验证码参数的请求时,会触发autoFillCaptchaIfPossible方法。接下来,我们将实现最关键的图像处理和OCR识别部分。
3.3 图像预处理与OCR识别引擎集成
我们需要在插件中集成图像处理逻辑。由于在Java中直接进行复杂的图像处理较为繁琐,一个常见的做法是通过命令行调用外部Python脚本来完成预处理和OCR识别。这样可以利用Python在数据科学和图像处理领域的丰富生态(如OpenCV, PIL, pytesseract)。插件负责提取图像数据并传递给Python脚本,然后接收识别结果。
首先,创建一个Python脚本ocr_engine.py:
#!/usr/bin/env python3 import sys import cv2 import numpy as np import pytesseract from PIL import Image import base64 import json import tempfile import os def preprocess_image(image_data): """ 对验证码图片进行预处理 :param image_data: 二进制图片数据 :return: 预处理后的二值化图像 (OpenCV格式) """ # 将二进制数据转换为numpy数组 nparr = np.frombuffer(image_data, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: raise ValueError("无法解码图像数据") # 1. 转为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 应用自适应阈值二值化,比全局阈值更能应对光照不均 # 参数:灰度图,最大阈值,自适应方法,阈值类型,块大小,常数C binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # 3. 降噪:使用中值滤波去除椒盐噪声 denoised = cv2.medianBlur(binary, 3) # 4. 形态学操作:开运算(先腐蚀再膨胀)去除细小白点(噪声) # 闭运算(先膨胀再腐蚀)填充小黑洞(字符内部) kernel = np.ones((2, 2), np.uint8) opened = cv2.morphologyEx(denoised, cv2.MORPH_OPEN, kernel) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel) # 可选:如果背景是深色,文字是浅色,需要反转 # 统计像素,如果黑色(0)像素远多于白色(255)像素,说明背景是黑色,需要反转 # if np.sum(closed == 0) > np.sum(closed == 255): # closed = cv2.bitwise_not(closed) return closed def ocr_captcha(image_data): """ 主识别函数 :param image_data: 二进制图片数据 :return: 识别出的文本字符串 """ try: processed_img = preprocess_image(image_data) # 配置Tesseract参数 # --psm 8: 将图像视为单个单词 # --oem 3: 默认OCR引擎模式 # -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ: 只识别数字和大写字母 custom_config = r'--oem 3 --psm 8 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' # 使用pytesseract进行OCR text = pytesseract.image_to_string(processed_img, config=custom_config) # 清理识别结果:去除空格、换行等无关字符 cleaned_text = ''.join(ch for ch in text if ch.isalnum()) return cleaned_text except Exception as e: return f"OCR_ERROR: {str(e)}" if __name__ == "__main__": # 从标准输入读取Base64编码的图片数据(Burp插件传递过来) input_data = sys.stdin.buffer.read() try: # 期望输入是JSON格式:{"image_b64": "..."} input_json = json.loads(input_data.decode('utf-8')) image_b64 = input_json.get("image_b64", "") if not image_b64: # 如果不是JSON,假设整个输入就是base64字符串(简化处理) image_b64 = input_data.decode('utf-8').strip() image_bytes = base64.b64decode(image_b64) result = ocr_captcha(image_bytes) # 输出结果到标准输出 sys.stdout.write(json.dumps({"success": True, "text": result})) except Exception as e: sys.stdout.write(json.dumps({"success": False, "error": str(e)})) sys.stdout.flush()然后,在Burp插件的Java代码中,我们需要实现调用这个Python脚本的逻辑。修改BurpExtender.java,添加以下方法:
private void handleCaptchaImage(IHttpRequestResponse messageInfo) { IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse()); byte[] responseBytes = messageInfo.getResponse(); int bodyOffset = responseInfo.getBodyOffset(); // 提取响应体(图片数据) byte[] imageBytes = Arrays.copyOfRange(responseBytes, bodyOffset, responseBytes.length); // 将图片数据Base64编码,准备传递给Python脚本 String imageBase64 = Base64.getEncoder().encodeToString(imageBytes); // 调用外部Python进程 String ocrResult = callPythonOCR(imageBase64); if (ocrResult != null && !ocrResult.startsWith("OCR_ERROR")) { log("验证码识别成功: " + ocrResult); // 存储识别结果和对应的图片哈希(或请求标识) lastCaptchaText = ocrResult; lastCaptchaImageHash = generateImageHash(imageBytes); // 简易哈希函数 // 可以更新UI显示 SwingUtilities.invokeLater(() -> { // 在UI上显示识别结果 }); } else { log("验证码识别失败: " + ocrResult); } } private String callPythonOCR(String imageBase64) { String pythonScriptPath = "/path/to/your/ocr_engine.py"; // 必须修改为实际路径 ProcessBuilder pb = new ProcessBuilder("python3", pythonScriptPath); pb.redirectErrorStream(true); try { Process process = pb.start(); // 向进程输入数据 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) { // 发送JSON格式数据 String inputJson = "{\"image_b64\": \"" + imageBase64 + "\"}"; writer.write(inputJson); writer.flush(); } // 读取进程输出 StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.append(line); } } int exitCode = process.waitFor(); if (exitCode == 0) { // 解析Python脚本返回的JSON String jsonOutput = output.toString(); // 使用简单的JSON解析库,例如org.json (需要添加依赖) // 这里为简化,假设返回格式为 {"success":true, "text":"ABC123"} if (jsonOutput.contains("\"success\":true")) { // 简单提取文本,实际应用应使用JSON解析器 int textStart = jsonOutput.indexOf("\"text\":\"") + 8; int textEnd = jsonOutput.indexOf("\"", textStart); return jsonOutput.substring(textStart, textEnd); } } return "OCR_ERROR: Process exited with code " + exitCode + ". Output: " + output; } catch (IOException | InterruptedException e) { return "OCR_ERROR: " + e.getMessage(); } } private void autoFillCaptchaIfPossible(IHttpRequestResponse messageInfo, IParameter captchaParam) { if (lastCaptchaText != null && !lastCaptchaText.isEmpty()) { // 使用存储的验证码文本更新请求参数 byte[] originalRequest = messageInfo.getRequest(); IRequestInfo analyzedReq = helpers.analyzeRequest(originalRequest); // 构建新的参数列表 List<IParameter> newParams = new ArrayList<>(); for (IParameter p : analyzedReq.getParameters()) { if (p.getName().equals(captchaParam.getName())) { // 替换验证码参数的值 newParams.add(helpers.buildParameter(p.getName(), lastCaptchaText, p.getType())); } else { newParams.add(p); } } // 使用新的参数重新构建请求体 byte[] newRequestBody = helpers.buildParameters(newParams); byte[] newRequest = helpers.buildHttpMessage(analyzedReq.getHeaders(), newRequestBody); // 更新原始消息 messageInfo.setRequest(newRequest); log("已自动填充验证码: " + lastCaptchaText + " 到参数 " + captchaParam.getName()); // 清空存储,避免重复使用 lastCaptchaText = null; lastCaptchaImageHash = null; } }3.4 插件UI增强与配置管理
一个完整的插件还需要一个友好的界面来展示日志、控制开关、配置参数。我们可以扩展之前的UI,添加以下功能:
- 日志面板:已经实现,用于显示插件运行状态、识别结果和错误信息。
- 配置面板:
- Python脚本路径:让用户指定
ocr_engine.py的绝对路径。 - Tesseract命令路径(如果Python脚本需要):或者直接在插件中配置Tesseract路径。
- 触发规则:允许用户自定义哪些URL或Content-Type触发识别。
- 参数名规则:自定义哪些请求参数名会被视为验证码字段。
- 预处理开关:允许用户启用/禁用某些预处理步骤(如二值化、降噪),并调整参数(如阈值、滤波核大小)。
- Python脚本路径:让用户指定
- 手动操作区:
- “测试识别”按钮:允许用户从Burp的响应中手动选择一张图片,点击按钮进行识别测试,实时查看预处理效果和识别结果。
- “复制结果”按钮:一键复制识别出的验证码到剪贴板。
- “自动填充”开关:控制插件是否自动修改请求。
这部分涉及大量Swing UI代码,核心是创建各种JTextField、JCheckBox、JButton,并将它们的值保存到插件的配置对象中(可以使用callbacks.saveExtensionSetting和callbacks.loadExtensionSetting进行持久化)。UI的完善可以极大提升插件的易用性。
4. 图像预处理策略深度优化
直接调用Tesseract识别原始验证码图片,成功率可能不足50%。预处理是提升识别率的关键。我们需要根据常见的验证码类型,设计针对性的预处理流水线。
4.1 常见验证码类型与应对策略
简单数字/字母(无干扰或轻微噪声):
- 策略:二值化 + 轻微降噪即可。重点在于找到最佳的二值化阈值。可以尝试全局阈值(
cv2.threshold)和自适应阈值(cv2.adaptiveThreshold),后者通常效果更好。 - 技巧:如果字符颜色与背景对比明显但颜色不定,可以先转换到HSV或LAB颜色空间,对特定通道进行阈值分割。
- 策略:二值化 + 轻微降噪即可。重点在于找到最佳的二值化阈值。可以尝试全局阈值(
带有干扰线/点的验证码:
- 策略:降噪是核心。中值滤波(
cv2.medianBlur)对椒盐噪声和细线非常有效。形态学操作(开运算)可以去除比结构元小的白色噪声点。 - 技巧:如果干扰线是彩色的,而文字是黑色的,可以尝试先提取黑色通道或使用颜色过滤。
- 策略:降噪是核心。中值滤波(
字符粘连或扭曲的验证码:
- 策略:这是最难处理的情况之一。轻微的粘连可以通过形态学腐蚀(
cv2.erode)尝试分离,但可能损伤字符本身。更好的方法是调整Tesseract的--psm(页面分割模式)。对于单个扭曲的单词,可以尝试--psm 8(单字)或--psm 13(原始行)。 - 技巧:如果背景复杂,尝试使用边缘检测(如Canny)先找出文字轮廓,再填充轮廓生成二值图像。
- 策略:这是最难处理的情况之一。轻微的粘连可以通过形态学腐蚀(
背景复杂或有渐变色的验证码:
- 策略:需要更高级的分割技术。可以尝试:
- 大津法(Otsu‘s Binarization):自动计算最佳全局阈值。
- 局部自适应阈值:如前所述,
cv2.adaptiveThreshold能处理光照不均。 - 背景估计与减除:如果背景变化缓慢,可以用大核进行形态学开运算(先腐蚀再膨胀)来估计背景,然后用原图减去背景图。
- 策略:需要更高级的分割技术。可以尝试:
4.2 构建可配置的预处理管道
我们可以将预处理步骤模块化,允许用户通过UI动态组合和调整。在Python脚本中,可以设计一个配置对象:
class PreprocessConfig: def __init__(self): self.grayscale = True self.denoise_method = 'median' # 'median', 'gaussian', 'bilateral', 'none' self.denoise_kernel = 3 self.binarize_method = 'adaptive' # 'global', 'adaptive', 'otsu' self.binarize_block_size = 11 self.binarize_c = 2 self.morph_ops = [] # e.g., [('open', 2), ('close', 2)] self.whitelist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' self.psm = 8 def preprocess_image_with_config(image_data, config): img = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR) if config.grayscale: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if config.denoise_method == 'median': img = cv2.medianBlur(img, config.denoise_kernel) elif config.denoise_method == 'gaussian': img = cv2.GaussianBlur(img, (config.denoise_kernel, config.denoise_kernel), 0) # ... 其他降噪方法 if config.binarize_method == 'adaptive': img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, config.binarize_block_size, config.binarize_c) # ... 其他二值化方法 for op, kernel_size in config.morph_ops: kernel = np.ones((kernel_size, kernel_size), np.uint8) if op == 'open': img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) elif op == 'close': img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) return imgBurp插件的UI可以生成这个配置的JSON,并传递给Python脚本。这样,测试人员可以针对不同的目标系统,保存多套预处理配置,实现“一键切换”。
4.3 效果评估与迭代调优
没有一套参数能通吃所有验证码。因此,插件需要提供一个“训练模式”或“调试面板”。
- 实时预览:在UI中,当用户从历史记录中选择一个包含验证码的响应时,插件可以加载图片,并应用当前的预处理配置,实时显示预处理后的效果图。
- 手动标注与反馈:当自动识别错误时,用户可以在UI中输入正确的验证码。插件可以记录下“原始图片-正确文本”这对数据,保存到本地数据集。
- 批量测试:用户可以导入一批保存的验证码图片和对应正确文本,让插件用当前配置批量识别,并计算准确率。这能科学地评估预处理策略和参数的有效性。
- 参数网格搜索(高级):对于追求极致效果的用户,可以设计一个简单的自动化脚本,对关键参数(如二值化块大小、C值、形态学核大小)进行排列组合,在小型测试集上跑一遍,找出准确率最高的那组参数。
5. 高级功能与实战应用场景
5.1 与Burp Intruder的深度集成
Burp Intruder是进行暴力破解和模糊测试的利器。我们的插件可以与其无缝集成,实现验证码的“随用随填”。
作为Intruder的Payload Processor:我们可以开发一个
IIntruderPayloadProcessor,当Intruder发起攻击时,对于每一个请求Payload,处理器自动调用OCR引擎识别最新的验证码图片(需要插件能记住会话中最后一个验证码的地址或标识),并将识别结果插入到Payload的指定位置。这要求插件能维护一个简单的会话状态。在Intruder攻击前捕获验证码:更常见的模式是,先手动在浏览器或Repeater中完成一次登录流程,让插件捕获到验证码图片和对应的会话Cookie。然后,在Intruder中配置Payload时,将验证码参数设置为“从扩展生成的列表”或“递归搜索”(这需要更复杂的插件逻辑)。插件则根据会话,为每一次攻击迭代生成或复用验证码。
处理动态变化的验证码:有些系统每次请求验证码URL都会变化。插件需要能够解析HTML,自动从页面中提取验证码图片的URL(可能是相对路径,需要拼接),并自动请求该URL获取图片进行识别。这需要实现一个简单的HTML解析器(或使用jsoup等库)来查找
<img src="...">标签。
5.2 会话管理与上下文关联
在真实的Web应用中,验证码通常与一个会话(Session)绑定。识别出的验证码只能用于同一个会话的提交请求。插件需要具备基本的会话管理能力。
- Cookie关联:当插件从一个响应中识别验证码时,应同时记录该响应中的
Set-Cookie头部信息(或整个Cookie Jar状态)。当需要填充验证码到请求时,必须确保请求携带了正确的Cookie。插件可以自动从Burp的Cookie Jar中获取,或维护自己的简易会话映射。 - Token/ID关联:有些验证码会附带一个
captcha_id或token在表单中,提交时需要一并发送。插件在提取图片时,也需要从周围的HTML中解析出这个ID,并在后续回填时,将ID和识别出的文本一起填充。
5.3 错误处理与重试机制
网络请求和OCR识别充满不确定性,健壮的插件必须有完善的错误处理。
OCR识别失败:返回空字符串、乱码或置信度极低时,应视为失败。插件可以:
- 记录日志:在UI中高亮显示失败记录。
- 触发重试:自动使用不同的预处理参数(如调整阈值)重新识别一次。
- 降级处理:提示用户手动识别,并提供一键将图片复制到剪贴板或弹出放大窗口的功能。
网络或进程错误:调用Python脚本可能因路径错误、依赖缺失、权限问题而失败。插件应捕获这些异常,并在UI中给出清晰的错误提示,例如“无法启动OCR引擎,请检查Python路径和依赖”。
验证码过期:如果提交识别出的验证码后,服务器返回“验证码错误”或“验证码已过期”,插件应能解析这个响应,并自动触发重新获取和识别新验证码的流程。这需要插件能识别特定的HTTP响应模式。
6. 常见问题排查与性能优化
在实际使用中,你会遇到各种各样的问题。下面是一些典型问题及其解决思路。
6.1 识别准确率低
这是最常见的问题。
- 症状:识别出的文本牛头不对马嘴,或完全为空。
- 排查步骤:
- 检查原始图片:首先在Burp的Response中查看原始图片是否清晰可辨。如果人眼都难以识别,OCR就更难了。
- 检查预处理效果:使用插件的“测试识别”或“预览”功能,查看预处理后的二值图像。文字是否清晰连贯?背景噪声是否被有效去除?字符是否断裂或粘连?
- 调整预处理参数:
- 如果文字太细或断裂,尝试减小二值化的阈值或调整自适应阈值的
C值(使其更敏感),或者避免使用腐蚀操作。 - 如果背景噪声多,尝试增加降噪滤波器的核大小,或添加形态学开运算。
- 如果字符粘连,尝试轻微的腐蚀,或调整Tesseract的
--psm为7(单行文本)或8(单个单词)。
- 如果文字太细或断裂,尝试减小二值化的阈值或调整自适应阈值的
- 检查Tesseract语言包和白名单:确保安装了正确的语言数据(如
eng)。如果验证码只包含数字,将白名单设置为0123456789可以大幅提升准确率。 - 尝试不同的OCR引擎:如果Tesseract始终不理想,可以考虑集成其他开源引擎,如
EasyOCR(基于深度学习,对复杂场景效果更好,但体积和耗时更大)作为备选方案。
6.2 插件导致Burp卡顿或无响应
- 症状:开启插件后,Burp流量经过速度变慢,甚至界面卡死。
- 原因与解决:
- 同步阻塞调用:在
processHttpMessage中直接进行耗时的图像处理和OCR调用,会阻塞Burp的主事件线程。必须将OCR识别任务放到单独的线程(Thread)或使用线程池中执行。 - 频繁处理大图片:如果每个图片都进行全流程处理,流量大时会消耗大量CPU和内存。可以添加过滤条件,只处理特定URL或大小的图片。
- Python进程启动开销:每次识别都启动一个新的Python进程开销巨大。可以改为常驻进程模式:插件启动时,启动一个Python子进程,并通过标准输入输出(stdin/stdout)或本地Socket与之保持长期通信,避免反复创建销毁进程。
- 同步阻塞调用:在
6.3 无法触发识别或自动填充
- 症状:验证码图片出现了,但插件没反应;或者识别成功了,但请求参数没有被修改。
- 排查步骤:
- 检查触发规则:确认验证码图片的URL或响应Content-Type是否在插件的监听范围内。有些验证码可能是动态生成的
data:image/png;base64,...格式嵌入在HTML或JS中,这种情况需要解析HTML响应体。 - 检查请求匹配规则:确认需要填充的请求参数名(如
captcha、vcode)是否在插件的识别列表中。有些系统参数名可能是动态的或经过混淆。 - 检查会话上下文:验证码识别和请求填充是否发生在同一个Burp“站点”(同一域名)和同一个“会话”中?插件可能错误地关联了不同标签页或不同域的请求/响应。
- 查看插件日志:日志中是否记录了“发现图片响应”和“发现需要验证码的请求”?如果没有,说明监听逻辑有问题。如果有记录但没填充,检查
autoFillCaptchaIfPossible方法中的逻辑,特别是lastCaptchaText是否在正确的时间被设置和清空。
- 检查触发规则:确认验证码图片的URL或响应Content-Type是否在插件的监听范围内。有些验证码可能是动态生成的
6.4 性能优化实践
- 图像尺寸缩放:验证码图片通常很小(如200x80像素)。如果收到大图,可以先按比例缩放至一个合理的最大宽度(如400像素),再进行后续处理,能显著减少处理时间。
- 缓存机制:对于短时间内同一URL返回的相同验证码(可能由于页面刷新),可以计算图片的哈希值(如MD5),将识别结果缓存一段时间(如5秒),避免重复识别。
- 并行处理:如果使用线程池,可以同时处理多个图片识别任务,但要注意线程安全和对Burp API的调用。
- 选择性处理:在代理历史记录中,可能有很多图片(如图标、logo)。插件应优先处理那些来自登录、注册、密码找回等关键URL的图片,可以通过配置URL关键词列表来实现。
开发这样一个插件的过程,本身就是一次对HTTP协议、图像处理、安全测试工具链的深度实践。它不会总是成功,但对于那些防护薄弱的系统,它能成为穿透防线的有效利器。更重要的是,通过构建它,你将对验证码的生成、传输、验证逻辑有更透彻的理解,这在寻找验证码逻辑漏洞(如不失效、可重复使用、客户端校验等)时,会带来意想不到的视角优势。