Burp Suite自定义插件开发实战:从请求拦截到加解密处理
1. 项目概述:为什么我们需要自定义Burp插件来拦截请求?
在安全测试和渗透测试的日常工作中,Burp Suite几乎是每个从业者手中的“瑞士军刀”。它的代理、重放、扫描功能强大,但你是否遇到过这样的场景:目标应用的请求参数被层层加密,Burp抓到的只是一堆乱码;或者,你需要对特定模式的请求(比如所有包含/api/v1/user的请求)自动添加一个测试用的认证头,手动操作繁琐且容易遗漏。这时,Burp自带的Intruder、Repeater虽然灵活,但缺乏自动化、定制化的深度处理能力。
这就是“Burp自定义插件实现请求拦截”项目的核心价值所在。它不是一个简单的功能使用教程,而是一次从“工具使用者”到“工具定制者”的思维跃迁。通过编写自己的插件,你可以让Burp按照你的逻辑,在请求发出前或响应返回后,自动进行修改、记录、解密或触发其他动作。这就像给你的Burp装上了一双“智能手”,让它能处理那些标准功能无法覆盖的、高度定制化的测试需求。
从网络热词中,我们可以看到大量相关的实践痛点:vue3自定义打印模板插件反映了前端场景下对请求/响应内容定制化处理的需求;若依微服务登录报403则暗示了在复杂微服务架构中,请求可能在网关层被拦截,需要深入分析请求链,自定义插件可以帮助我们插入诊断逻辑;postman实现拦截浏览器请求更是直接点明了“请求拦截”这一核心动作在不同工具间的通用性。而ai + skill + burp实战则预示着,结合AI能力(比如用Codex生成插件代码)来提升Burp插件的开发效率,正成为一个新的趋势。
简单来说,这个项目就是教你如何用Java(或Python/Jython)为Burp Suite编写一个扩展(Extension),重点实现IHttpListener或IMessageEditorTab等接口,从而在HTTP请求/响应的生命周期中植入你的自定义逻辑。接下来,我会以一个实战案例为主线,拆解从环境搭建、代码编写、调试到打包部署的全过程,并分享我踩过的坑和积累的技巧。
2. 核心需求与场景拆解:你的插件到底要解决什么问题?
在动手写代码之前,明确需求是第一步。盲目开发一个“万能”插件往往事倍功半。根据我的经验,自定义Burp插件的需求主要集中在这几个方面:
2.1 加解密/编码解码场景
这是最常见、最刚需的场景。很多移动端APP或前后端分离的Web应用,为了安全或防篡改,会对通信数据进行加密(如AES、RSA)或签名。Burp抓到的包是密文,你无法直接修改参数进行测试。此时,你需要一个插件,在请求到达Burp时自动解密,让你看到明文并进行修改;在Burp将修改后的请求发往服务器前,再自动加密回去。同理,响应也是如此。这直接对应了搜索片段中“解密加密的请求参数和响应结果”的需求。
2.2 自动化修改与注入场景
测试中经常需要批量修改请求。例如:
- 自动添加头部:为所有请求加上
X-Forwarded-For: 127.0.0.1或特定的JWT Token。 - 参数污染:自动为所有GET/POST参数添加一个测试值,如
&test=payload。 - 替换特定内容:将请求体中的某个固定值(如版本号
v1.0)自动替换为v2.0进行测试。 - 会话管理:当检测到响应返回
401时,自动调用登录接口刷新Token,并更新后续请求的Header。
2.3 流量分析与监控场景
你需要对符合特定条件的流量进行记录、统计或告警。
- 敏感信息监控:扫描所有请求和响应,记录其中可能出现的身份证号、手机号、邮箱等敏感信息。
- API端点发现:自动记录所有访问过的URL路径,并去重整理,用于绘制攻击面。
- 异常检测:监控响应状态码(如大量5xx错误)或特定错误信息,并在Burp的Alerts标签页给出提示。
2.4 与其他工具联动场景
这就是burp联动xray、用codex接入burp mcp这类热词背后的含义。你可以开发插件作为“粘合剂”,让Burp与其他安全工具(如漏洞扫描器、子域名爆破工具、AI辅助审计工具)进行数据交换和协同工作。例如,插件可以将Burp捕获的请求实时发送给Xray进行被动扫描,或者将请求内容发送给一个本地运行的AI模型,让其分析是否存在潜在漏洞。
注意:在明确需求时,务必考虑插件的性能影响。拦截和处理每一个请求/响应都会消耗CPU和内存。对于高流量场景,你的处理逻辑必须高效,避免造成Burp卡顿甚至崩溃。我个人的经验是,先实现核心功能,再考虑优化,比如添加开关、针对特定域名生效等。
3. 开发环境搭建与Burp Extender API初探
工欲善其事,必先利其器。Burp插件主流开发语言是Java,因为Burp本身是Java写的,原生支持最好。当然,你也可以用Python(通过Jython),但性能和生态稍逊一筹。这里我们以Java为例。
3.1 环境准备清单
- JDK:安装Java 8或11(推荐11,兼容性最好)。确保
java和javac命令可用。 - IDE:IntelliJ IDEA(社区版免费,对Java支持极佳)或Eclipse。IDEA的自带构建工具更简单。
- Burp Suite Professional:你需要一个Burp专业版来加载和测试插件。社区版功能受限,不适合开发调试。
- 构建工具:Maven或Gradle。我推荐Maven,管理依赖非常方便。我们将创建一个Maven项目。
3.2 创建Maven项目并配置依赖
在IDEA中新建一个Maven项目,groupId和artifactId自定,比如com.security.burp.demo。 关键步骤是配置pom.xml,引入Burp提供的扩展API。注意:Burp没有将它的API发布到公共的Maven仓库。通常有两种方式:
- 方式一(推荐):从Burp安装目录中提取
burpsuite_pro.jar(或burpsuite_community.jar)中的API接口类,并安装到本地Maven仓库。- 找到你的Burp安装目录,复制
burpsuite_pro.jar到某个位置。 - 使用Maven命令将其安装到本地库:
这里的版本号mvn install:install-file -Dfile=/path/to/burpsuite_pro.jar -DgroupId=net.portswigger -DartifactId=burp-extender-api -Dversion=2024.3 -Dpackaging=jar2024.3可以按你的Burp版本修改。
- 找到你的Burp安装目录,复制
- 方式二:直接将Burp的Jar文件作为项目的依赖库(Library),不进行Maven安装。这种方式在IDEA中也可以,但不利于项目管理和团队协作。
采用方式一后,在pom.xml中添加依赖:
<dependencies> <dependency> <groupId>net.portswigger</groupId> <artifactId>burp-extender-api</artifactId> <version>2024.3</version> <!-- 与你安装的版本一致 --> <scope>provided</scope> <!-- 重要!因为Burp运行时已经提供了这些类 --> </dependency> </dependencies><scope>provided</scope>意味着这个依赖在编译和测试时需要,但不会被打进最终的插件Jar包,因为Burp环境本身已包含。
3.3 理解核心API:IHttpListener
Burp的Extender API有很多接口,对于“请求拦截”,最核心的是burp.IHttpListener。它的作用是在Burp处理HTTP请求和响应的过程中,提供一个回调入口。
package burp; public interface IHttpListener { void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo); }toolFlag:一个整数标志,告诉你当前消息来自Burp的哪个工具(如IBurpExtenderCallbacks.TOOL_PROXY代表代理流量,TOOL_REPEATER代表Repeater等)。你可以根据这个标志决定是否处理该消息。messageIsRequest:布尔值,true表示当前处理的是请求,false表示是响应。messageInfo:IHttpRequestResponse对象,它包含了完整的请求/响应信息,并且最关键的是,它是可修改的。你可以通过它的getRequest()和setRequest()方法来读取和修改请求内容;响应同理。
除了IHttpListener,还有其他常用接口:
IMessageEditorTab:用于在Burp的请求/响应编辑器(如Repeater、Intruder)中创建自定义的标签页,常用于显示解密后的内容或提供自定义的编辑界面。IScannerCheck:用于创建自定义的主动扫描检查项。IContextMenuFactory:用于在右键菜单中添加自定义项。
我们的第一个插件将聚焦于IHttpListener,实现一个简单的请求/响应修改器。
4. 实战:编写一个自动修改User-Agent的拦截插件
让我们从一个最简单的例子开始:编写一个插件,自动将所有经过Burp代理的请求的User-Agent头,修改为我们自定义的字符串。这个例子虽小,但涵盖了插件开发的所有核心步骤。
4.1 创建主类并实现IBurpExtender
每个Burp插件都必须有一个实现了IBurpExtender接口的主类。这个接口只有一个方法registerExtenderCallbacks,Burp在加载插件时会调用它。
package com.security.burp.demo; import burp.*; import java.io.PrintWriter; public class BurpExtender implements IBurpExtender, IHttpListener { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private PrintWriter stdout; private PrintWriter stderr; // 自定义的User-Agent private static final String CUSTOM_USER_AGENT = "MySecurityScanner/1.0 (Custom Burp Plugin)"; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { // 保存callbacks和helpers引用,它们是与Burp交互的桥梁 this.callbacks = callbacks; this.helpers = callbacks.getHelpers(); // 设置扩展名称,这会在Burp的Extender标签页中显示 callbacks.setExtensionName("Auto User-Agent Modifier"); // 获取标准输出和错误流,用于插件调试信息输出 stdout = new PrintWriter(callbacks.getStdout(), true); stderr = new PrintWriter(callbacks.getStderr(), true); // 注册自己为HTTP监听器 callbacks.registerHttpListener(this); // 打印加载成功信息 stdout.println("[+] Auto User-Agent Modifier plugin loaded successfully!"); } }代码解读:
- 主类
BurpExtender同时实现了IBurpExtender和IHttpListener接口。 registerExtenderCallbacks是入口点。我们通过callbacks对象注册自己为HTTP监听器。IExtensionHelpers是一个工具类,提供了很多解析和构建HTTP消息的便捷方法,后面会用到。- 使用
callbacks.getStdout()和getStderr()获取的输出流,其内容会显示在Burp的Extender标签页的“Output”和“Errors”子标签中,这是插件调试最重要的手段。
4.2 实现processHttpMessage方法
这是拦截和处理请求/响应的核心。
@Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { // 只处理来自代理工具(Proxy)的请求,忽略Repeater、Intruder等工具的流量 // 你可以根据需要修改这个条件,比如 TOOL_PROXY | TOOL_REPEATER if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY) { return; } // 只处理HTTP请求 if (!messageIsRequest) { return; } // 获取当前的请求信息对象 IRequestInfo requestInfo = helpers.analyzeRequest(messageInfo); // 获取请求的头部列表 java.util.List<String> headers = requestInfo.getHeaders(); // 遍历头部,寻找并替换User-Agent boolean userAgentFound = false; for (int i = 0; i < headers.size(); i++) { String header = headers.get(i); // 不区分大小写地查找User-Agent头 if (header.toLowerCase().startsWith("user-agent:")) { headers.set(i, "User-Agent: " + CUSTOM_USER_AGENT); userAgentFound = true; stdout.println("[*] Modified User-Agent for request to: " + requestInfo.getUrl()); break; // 找到并修改后退出循环 } } // 如果原始请求中没有User-Agent头,则添加一个 if (!userAgentFound) { headers.add("User-Agent: " + CUSTOM_USER_AGENT); stdout.println("[+] Added User-Agent for request to: " + requestInfo.getUrl()); } // 获取原始的请求体(body) byte[] requestBytes = messageInfo.getRequest(); int bodyOffset = requestInfo.getBodyOffset(); byte[] body = new byte[requestBytes.length - bodyOffset]; System.arraycopy(requestBytes, bodyOffset, body, 0, body.length); // 使用helpers.buildHttpMessage方法,用新的头部和原始的请求体重建完整的请求字节数组 byte[] newRequest = helpers.buildHttpMessage(headers, body); // 将修改后的请求设置回messageInfo messageInfo.setRequest(newRequest); }代码解读与注意事项:
- 工具过滤:
if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY)这行代码非常重要。它确保插件只处理从浏览器/APP发来的代理流量。如果不加过滤,你在Repeater里手动发的请求也会被插件修改,这通常不是我们想要的。你可以用位或运算|来组合多个工具标志。 - 请求/响应判断:
if (!messageIsRequest)确保我们只修改请求,不修改响应。 - 使用IExtensionHelpers分析请求:
helpers.analyzeRequest(messageInfo)返回一个IRequestInfo对象,它帮你解析了请求方法、URL、头部列表、参数等信息,非常方便。切忌自己用字符串分割的方式去解析HTTP请求,很容易出错。 - 头部修改:头部列表
List<String>中的每个元素是一个完整的头部行,如User-Agent: Mozilla/5.0...。修改时,需要保持Header: Value的格式。 - 请求体重建:这是最容易出错的一步。
requestInfo.getBodyOffset()返回请求体在原始字节数组中的起始位置。我们必须分离出头部和体部,然后用新的头部和原始的体部,通过helpers.buildHttpMessage方法重建完整的请求。直接修改headers列表不会自动生效,必须调用setRequest。 - 日志输出:使用
stdout.println输出调试信息,这在排查问题时至关重要。
4.3 编译与打包
在IDEA中,你可以直接使用Maven进行打包:
mvn clean compile package这会在target目录下生成一个Jar文件,比如demo-1.0-SNAPSHOT.jar。这个Jar需要包含你所有的依赖(除了scope为provided的)。因为我们只依赖了Burp API(scope=provided),所以打出来的Jar包只包含我们自己的类,非常轻量。
4.4 在Burp中加载与测试
- 打开Burp Suite,进入Extender标签页 ->Extensions子标签。
- 点击Add按钮。
- 在Extension Type下拉框中选择Java。
- 点击Select file...,选择你刚刚打包好的Jar文件。
- 点击Next。如果一切正常,Burp会解析你的插件,并在下方输出“Extension loaded successfully”。你也会在Loaded Extensions列表中看到你的插件名“Auto User-Agent Modifier”。
- 打开Proxy->Intercept,确保拦截是开启的。
- 用浏览器访问任意网站,流量经过Burp。在Intercept标签页,你应该能看到经过你插件的请求,其
User-Agent头已经被替换成了MySecurityScanner/1.0 (Custom Burp Plugin)。同时,在Extender的Output标签页,能看到插件打印的日志。
至此,你的第一个Burp拦截插件就成功运行了!这个过程虽然简单,但已经搭建起了插件开发的基本框架。
5. 进阶实战:实现一个简易的请求参数解密/加密插件
现在我们来挑战一个更实用、也更复杂的场景:处理加密的请求参数。假设目标应用对POST请求的JSON体进行了简单的Base64编码(这是一种简化的例子,实际可能是AES,但原理相通)。我们的插件需要:
- 在Burp界面显示解密后的明文。
- 允许我们在明文上修改。
- 在我们发送请求前,自动将修改后的明文重新加密。
这需要用到IMessageEditorTab接口,在Repeater等工具的编辑器里创建一个新的标签页。
5.1 设计思路与类结构
我们将创建两个主要的类:
BurpExtender(主类):实现IBurpExtender和IMessageEditorTabFactory。负责注册Tab工厂。DecryptMessageEditorTab:实现IMessageEditorTab。负责创建自定义编辑器标签页,显示解密后的内容,并处理加密回写。
5.2 实现IMessageEditorTabFactory
首先修改主类,注册一个能创建我们自定义Tab的工厂。
public class BurpExtender implements IBurpExtender, IMessageEditorTabFactory { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private PrintWriter stdout; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { this.callbacks = callbacks; this.helpers = callbacks.getHelpers(); callbacks.setExtensionName("Base64 Crypto Helper"); stdout = new PrintWriter(callbacks.getStdout(), true); // 注册消息编辑器Tab工厂 callbacks.registerMessageEditorTabFactory(this); stdout.println("[+] Base64 Crypto Helper plugin loaded."); } @Override public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) { // 返回我们自定义的Tab实例 // editable参数很重要,它告诉我们的Tab,当前上下文是否允许编辑(比如在Repeater中就是true) return new DecryptMessageEditorTab(controller, editable, helpers, stdout); } }5.3 实现自定义的DecryptMessageEditorTab
这是核心部分,代码较长,我们分段解析。
class DecryptMessageEditorTab implements IMessageEditorTab { private final IMessageEditorController controller; private final boolean editable; private final IExtensionHelpers helpers; private final PrintWriter stdout; // Burp提供的文本编辑器组件,我们将解密后的内容放在这里显示和编辑 private ITextEditor txtEditor; // 当前显示的字节内容(解密后的明文) private byte[] currentDisplayContent; // 当前编辑的原始消息(加密的请求或响应) private byte[] originalMessage; private boolean isRequest; public DecryptMessageEditorTab(IMessageEditorController controller, boolean editable, IExtensionHelpers helpers, PrintWriter stdout) { this.controller = controller; this.editable = editable; this.helpers = helpers; this.stdout = stdout; // 从callbacks中创建一个文本编辑器实例 this.txtEditor = helpers.createTextEditor(); this.txtEditor.setEditable(editable); } @Override public String getTabCaption() { // 返回Tab上显示的名称 return "Decrypted View"; } @Override public javax.swing.Component getUiComponent() { // 返回Tab的UI组件,就是我们的文本编辑器组件 return txtEditor.getComponent(); } }接下来是实现关键方法setMessage和getMessage。
@Override public void setMessage(byte[] content, boolean isRequest) { // 当用户切换消息(比如在Repeater中发送了请求)时,Burp会调用此方法 // content: 原始的、完整的HTTP消息字节数组 // isRequest: 是否是请求 this.originalMessage = content; this.isRequest = isRequest; this.currentDisplayContent = null; // 清空当前显示内容 if (content == null || content.length == 0) { txtEditor.setText(null); txtEditor.setEditable(false); return; } try { // 1. 分析消息,获取头部和体部 IRequestInfo requestInfo = helpers.analyzeRequest(content); // 注意:如果是响应,需要用helpers.analyzeResponse,这里简化处理,假设都是请求 // 实际项目中需要根据isRequest判断 int bodyOffset = requestInfo.getBodyOffset(); byte[] body = new byte[content.length - bodyOffset]; System.arraycopy(content, bodyOffset, body, 0, body.length); // 2. 假设body是Base64编码的字符串,进行解密 String bodyStr = new String(body, "UTF-8").trim(); String decryptedStr; try { // 尝试Base64解码 byte[] decodedBytes = java.util.Base64.getDecoder().decode(bodyStr); decryptedStr = new String(decodedBytes, "UTF-8"); stdout.println("[*] Successfully decoded request body."); } catch (IllegalArgumentException e) { // 如果不是合法的Base64,则原样显示 decryptedStr = "[Not a valid Base64 payload]\n" + bodyStr; stdout.println("[!] Request body is not Base64 encoded."); } // 3. 将解密后的字符串设置为编辑器的内容 currentDisplayContent = decryptedStr.getBytes("UTF-8"); txtEditor.setText(currentDisplayContent); txtEditor.setEditable(editable && isRequest); // 通常只允许编辑请求 } catch (Exception e) { stdout.println("[ERROR] Failed to process message: " + e.getMessage()); e.printStackTrace(stdout); txtEditor.setText(("Error: " + e.toString()).getBytes()); txtEditor.setEditable(false); } } @Override public byte[] getMessage() { // 当用户点击其他Tab(如Raw)或发送请求时,Burp会调用此方法获取修改后的消息 // 我们需要将编辑器中修改后的明文,重新加密,并替换回原始消息的体部 if (!isModified()) { // 如果内容没被修改,直接返回原始消息 return originalMessage; } if (!isRequest) { // 对于响应,我们通常不修改(或者修改逻辑不同),这里直接返回原始 stdout.println("[!] Not modifying response messages."); return originalMessage; } try { // 1. 获取编辑器中修改后的明文 byte[] editedContent = txtEditor.getText(); if (editedContent == null) { return originalMessage; } String editedStr = new String(editedContent, "UTF-8"); // 2. 重新加密(这里用Base64编码) String reEncryptedStr = java.util.Base64.getEncoder().encodeToString(editedStr.getBytes("UTF-8")); // 3. 重建完整的HTTP请求消息 IRequestInfo originalRequestInfo = helpers.analyzeRequest(originalMessage); java.util.List<String> headers = originalRequestInfo.getHeaders(); // 注意:这里需要根据实际情况判断是否需要更新Content-Length头 // helpers.analyzeRequest不会自动更新,我们需要手动处理或依赖Burp byte[] newMessage = helpers.buildHttpMessage(headers, reEncryptedStr.getBytes("UTF-8")); stdout.println("[+] Successfully re-encoded and updated request."); return newMessage; } catch (Exception e) { stdout.println("[ERROR] Failed to re-construct message: " + e.getMessage()); return originalMessage; // 出错时返回原始消息,避免破坏请求 } } @Override public boolean isModified() { // 判断编辑器中的内容是否被用户修改过 return txtEditor.isTextModified(); } @Override public byte[] getSelectedData() { // 返回编辑器中选中的数据,一般用不上,返回null即可 return null; }关键点与避坑指南:
setMessage与getMessage的调用时机:setMessage是Burp把原始消息给你,让你展示。getMessage是Burp向你要修改后的消息。理解这个“一问一答”的流程至关重要。- 区分请求和响应:
setMessage的isRequest参数指明了当前消息是请求还是响应。我们的解密逻辑可能只适用于请求体。对于响应,可能需要不同的处理逻辑,或者直接禁用编辑。代码中做了简单判断,实际应用需要更严谨。 - 编码问题:HTTP消息是字节流。在将字节数组转换为字符串进行解密时,必须明确编码(如UTF-8)。同样,加密后转回字节数组也要保持一致。否则会出现乱码。
Content-Length头:当我们修改了请求体长度后,原始的Content-Length头就失效了。helpers.buildHttpMessage方法不会自动帮你更新这个头。这是一个大坑!对于application/x-www-form-urlencoded或JSON等格式,Burp的Proxy可能在某些情况下会处理,但为了绝对可靠,我们应该自己更新。可以在getMessage中,遍历headers列表,找到Content-Length头,计算新体部的字节长度并更新其值。- 错误处理:加解密过程可能失败(如非法字符)。必须用
try-catch包裹,并在出错时给出明确提示(通过stdout打印日志或编辑器显示错误信息),并尽量返回原始消息,避免导致Burp无法发送请求。 - 性能考虑:
setMessage和getMessage会被频繁调用。内部的加解密算法如果很复杂(如AES),可能会影响Burp的响应速度。可以考虑缓存结果或添加开关。
5.4 打包测试
重复编译打包流程,在Burp中加载新插件。然后:
- 在Repeater中,构造一个POST请求,Body里放一个Base64编码的字符串,例如
eyJ1c2VybmFtZSI6ICJ0ZXN0IiwgInBhc3N3b3JkIjogIjEyMzQ1NiJ9(这是{"username": "test", "password": "123456"}的Base64编码)。 - 发送请求,然后在请求或响应面板,你应该能看到多了一个标签页“Decrypted View”。
- 点击这个标签页,里面显示的是解密后的JSON明文。
- 修改明文中的值,比如把
"test"改成"admin"。 - 点击其他标签页(如Raw)或再次点击“Send”,Burp会自动调用
getMessage方法。 - 观察Raw视图,你会发现请求体已经变成了修改后明文的Base64编码形式(
eyJ1c2VybmFtZSI6ICJhZG1pbiIsICJwYXNzd29yZCI6ICIxMjM0NTYifQ==)。 - 发送请求,服务器收到的就是修改后的密文了。
这个插件极大地简化了对加密接口的测试工作,无需再手动编解码。
6. 插件开发中的高级技巧与常见问题排查
经过两个实战,你已经掌握了Burp插件开发的基础。但在实际项目中,还会遇到更多复杂情况。下面分享一些进阶技巧和排坑经验。
6.1 如何持久化存储配置?
你的插件可能需要保存一些设置,比如加解密的密钥、开关状态、目标域名等。Burp提供了IExtensionStateListener接口和callbacks.saveExtensionSetting()、callbacks.loadExtensionSetting()方法来实现持久化。
public class BurpExtender implements IBurpExtender, IExtensionStateListener { private static final String SETTING_KEY = "myplugin.config"; @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { // ... 其他初始化 ... callbacks.registerExtensionStateListener(this); // 加载配置 String savedConfig = callbacks.loadExtensionSetting(SETTING_KEY); if (savedConfig != null) { // 解析并应用配置 stdout.println("Loaded config: " + savedConfig); } } @Override public void extensionUnloaded() { // 插件被卸载时调用,可以在这里保存配置 String configToSave = "your_config_string"; callbacks.saveExtensionSetting(SETTING_KEY, configToSave); stdout.println("Configuration saved."); } }6.2 创建自定义UI(Swing)
对于配置复杂的插件,一个图形界面是必要的。Burp插件使用Java Swing来创建UI。你可以在registerExtenderCallbacks中创建JPanel,然后通过callbacks.addSuiteTab(new ITab){...}将其添加到Burp的主界面作为一个新标签页,或者通过callbacks.createMessageEditor()创建一个嵌入式的编辑器。
注意:Swing的UI操作必须在事件分发线程(Event Dispatch Thread, EDT)上执行。可以使用
SwingUtilities.invokeLater()来包装你的UI创建代码,避免线程问题导致界面卡死。
6.3 处理HTTPS与代理链
如果你的插件需要对外发起网络请求(比如调用一个外部解密服务),需要注意Burp可能处于一个复杂的代理环境中。你可以使用callbacks.getHelpers().buildHttpRequest()和callbacks.makeHttpRequest()方法,它们会尊重Burp的代理设置和TLS证书配置。
6.4 常见问题排查实录
问题1:插件加载失败,Output显示“Error: no interface”或“java.lang.UnsupportedClassVersionError”。
- 原因:最常见的原因是JDK版本不兼容。你用高版本JDK(如JDK 17)编译了插件,但Burp自带的JRE可能是旧版本(如Java 11)。
- 解决:在Maven的
pom.xml中指定编译的字节码版本。
或者直接在IDEA的Project Structure中设置项目语言级别为11。<properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>
问题2:插件能加载,但不起作用,没有日志输出。
- 原因1:没有正确注册监听器或工厂。检查
registerExtenderCallbacks中是否调用了callbacks.registerHttpListener(this)或registerMessageEditorTabFactory(this)。 - 原因2:在
processHttpMessage中过早return。检查你的工具过滤(toolFlag)和请求/响应判断(messageIsRequest)逻辑是否过于严格,把需要处理的消息过滤掉了。可以暂时注释掉过滤逻辑进行测试。 - 原因3:代码有异常,但被吞掉了。确保用
try-catch包裹核心逻辑,并在catch中用stderr.println(e)打印堆栈信息。
问题3:修改了请求,但服务器收到的还是原始请求。
- 原因1:在
processHttpMessage中,只修改了headers变量,但没有调用messageInfo.setRequest(newRequest)。修改headers列表后必须重建请求并set回去。 - 原因2:在
IMessageEditorTab的getMessage()方法中,isModified()返回了false,导致Burp没有获取修改后的内容。确保你的isModified()逻辑正确,并且txtEditor.isTextModified()能正确感知变化。 - 原因3:
Content-Length头没有更新。服务器可能因为长度不对而截断或拒绝请求。在getMessage()中重建请求后,手动更新Content-Length头。
问题4:插件导致Burp变卡,甚至无响应。
- 原因:
processHttpMessage或getMessage/setMessage中的处理逻辑太耗时(如复杂的加解密、频繁的IO操作)。 - 解决:
- 优化算法:检查加解密逻辑,避免在循环中重复创建对象。
- 添加缓存:对于相同输入输出,可以考虑缓存结果。
- 异步处理:对于非常耗时的操作,可以考虑启动新线程,但要注意线程安全,并且不能阻塞Burp的主事件线程。
- 添加开关:提供配置选项,让用户可以选择只对特定域名或特定类型的请求启用插件。
问题5:在Repeater中,自定义的MessageEditorTab有时不出现。
- 原因:
createNewInstance方法被调用时,editable参数可能为false(例如在某些只读视图下)。你的IMessageEditorTab实现可能因为!editable而返回了null或不可用的Tab。 - 解决:即使不可编辑,也尽量返回一个可以显示内容的Tab实例,只是将内部的编辑器设置为只读状态(
txtEditor.setEditable(false))。这能提供更好的用户体验。
开发Burp插件是一个需要耐心调试的过程。充分利用stdout/stderr输出日志,结合Burp的Extender标签页中的输出信息,是定位问题的关键。从简单的功能开始,逐步迭代,最终你就能打造出贴合自己工作流的强大武器。