1. 为什么你需要一个自己的Burpsuite加解密插件?
如果你经常做安全测试,尤其是渗透测试或者API接口测试,最近几年肯定被各种加密请求搞得头大。我自己的项目里,十个应用有八个都在请求体和响应体里玩起了AES、RSA、自定义签名这些花样。早期还能找到一些现成的插件凑合用,但要么年久失修,要么配置复杂得让人想放弃,最关键的是,它们往往跟不上你手头项目那“独特”的加解密逻辑。
这时候,自己动手开发一个定制化的Burpsuite加解密插件,就成了最高效的解决方案。它就像给你的Burpsuite装上了一双“透视眼”,能让加密的流量在你面前变成清晰的明文,拦截、修改、重放这些操作变得和测试普通HTTP请求一样简单。新版Burpsuite推出的Montoya API,更是让插件开发的门槛降低了不少,它设计得更现代、更清晰,哪怕你Java基础不是特别扎实,跟着流程走也能搞出个能用的插件来。
这篇文章,我就以一个实战过来人的身份,带你走一遍完整的开发流程。我们不谈空泛的理论,就从环境搭建、算法集成,一直讲到如何把加解密逻辑精准地“注入”到Burpsuite的请求/响应处理链中。我会分享我踩过的坑、调试的技巧,以及如何让插件既实用又稳定。目标很简单:让你看完就能动手,做出一个解决自己实际问题的插件。
2. 开发前的环境与思想准备
工欲善其事,必先利其器。在敲代码之前,把环境和思路理清楚,能省掉后面一大堆莫名其妙的报错时间。
2.1 软硬件环境清单
我的开发环境组合经过实践检验,比较稳定,你可以直接照搬:
- 操作系统:Windows 10 或 11, macOS 和 Linux 同样可行,但本文以 Windows 为例,路径分隔符需要注意。
- Burpsuite:必须使用支持 Montoya API 的版本,我使用的是 Burp Suite Community Edition 2024.5.3。你可以在 PortSwigger 官网下载到最新版。确保你的 Burp 是 2023.6 或更高版本,旧版的 Extender API 不兼容。
- Java 开发环境:JDK 17 或 21。Montoya API 对 Java 版本有要求,推荐使用 JDK 17 LTS 版本。安装后记得配置好
JAVA_HOME环境变量。 - 集成开发环境 (IDE):IntelliJ IDEA Community Edition。它对 Maven 和 Java 的支持非常友好,社区版完全免费且功能强大。版本我用的是 2023.2.3,新版也一样。
- 构建工具:Apache Maven。我们用它来管理项目依赖,特别是引入核心的 Montoya API 库。
- 辅助工具:Visual Studio Code 和 Node.js。这两个不是开发 Burp 插件的必需品,但如果你需要参考或调试目标网站的前端加密 JS 逻辑(这是常事),用它们来运行和分析 JS 加密代码会非常方便。
2.2 理解 Montoya API 的处理流程(核心思想)
在写第一行代码前,我们必须搞清楚 Burpsuite 处理流量的几个关键节点,以及我们的插件代码应该在哪个节点介入。这是整个插件开发的“地图”,理解错了,代码写得再对也没用。
新版 Montoya API 主要通过几个 Handler(处理器)来挂钩流量。对于我们加解密插件,最关键的是三个:
-
ProxyRequestHandler:处理经过 Burp Proxy 的请求。它有两个关键方法:handleRequestReceived:当浏览器发出的请求刚刚到达Burp Proxy 时触发。此时你看到的是浏览器发送的原始数据(可能是加密的)。这个点通常用于读取和存储原始请求,或者提取一些初始信息(比如从请求头里获取密钥),但一般不对请求体做解密修改。handleRequestToBeSent:当请求即将从Burp Proxy 发往目标服务器时触发。这是请求解密的黄金位置!因为用户在 Proxy 的拦截界面或 Repeater 里修改了数据后,会经过这里。我们需要在这里将用户输入的明文,重新加密成服务端期望的格式发出去。
-
HttpHandler:处理 Burp 工具(如 Repeater, Intruder)直接发出的 HTTP 请求,以及从服务器返回的响应。它也有两个关键方法:handleHttpRequestToBeSent:当从 Repeater 等工具发出的请求即将发送到服务器时触发。这里是另一个重要的请求加密点,必须确保从这里出去的请求是加密后的,否则服务端无法识别。handleHttpResponseReceived:当服务器的响应刚刚到达Burp 时触发。这是响应解密的黄金位置!我们在这里将服务端返回的加密响应解密成明文,供 Burp 界面(如 Proxy 历史、Repeater 响应面板)显示。
-
ProxyResponseHandler:处理从服务器返回、经过 Burp Proxy 再流向浏览器的响应。handleResponseReceived:服务器响应到达 Proxy 时触发。类似请求侧,这里一般用于记录。handleResponseToBeSent:响应即将发送回浏览器时触发。如果我们在HttpHandler里修改了响应(解密了),就需要在这里确保返回给浏览器的是解密后的明文,否则浏览器看不懂。
简单来说,一个完整的加解密闭环是:浏览器加密请求 -> ProxyRequestHandler (记录) -> 用户可能在Proxy界面修改 -> ProxyRequestHandler (解密为明文供用户看) -> HttpHandler (将明文重新加密后发出) -> 服务器处理 -> HttpHandler (将加密响应解密为明文) -> ProxyResponseHandler (将明文响应返回给浏览器)。
脑子里有了这张流程图,我们写代码就知道每一段逻辑该放在哪儿了。
3. 从零开始:创建你的第一个 Montoya 插件项目
接下来我们动手创建一个最基础的、能成功加载的插件项目。这是验证环境是否正确的关键一步。
3.1 在 IDEA 中创建 Maven 项目
打开 IntelliJ IDEA,选择 “New Project”。在左侧选择 “Maven”,确保 JDK 选择了你安装的 17 或 21 版本。GroupId 和 ArtifactId 可以按你喜欢的来,比如 com.security 和 mycrypto-plugin。点击创建,IDEA 会自动生成一个包含 pom.xml 文件的项目。
3.2 配置核心依赖:pom.xml
项目创建后,打开根目录下的 pom.xml 文件。这是 Maven 项目的“心脏”,所有依赖库都在这里声明。我们需要添加两个最重要的依赖:
- Montoya API:这是 Burpsuite 官方提供的插件开发接口库。
- 一个 JSON 处理库:比如 Fastjson2 或 Jackson,用于方便地解析和修改 JSON 格式的请求/响应体。这里我用 Fastjson2,因为它速度快且 API 简洁。
将 pom.xml 中的 <dependencies> 部分修改或补充为如下内容:
<dependencies>
<!-- Burpsuite Montoya API 核心依赖 -->
<dependency>
<groupId>net.portswigger.burp.extensions</groupId>
<artifactId>montoya-api</artifactId>
<version>2024.5</version> <!-- 请务必使用与你Burp版本匹配或稍旧的版本 -->
<scope>provided</scope> <!-- 关键!因为Burp运行时自带此库 -->
</dependency>
<!-- Fastjson2 用于JSON处理 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>
<!-- 日志框架,调试时非常有用 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
特别注意:montoya-api 的 scope 必须设置为 provided。这意思是“编译和测试时需要这个库,但打包时不要把它打进去”,因为 Burpsuite 启动时自己就带着这个 API 了。如果打包进去,反而可能导致类冲突,插件加载失败。
3.3 编写插件入口类:BurpExtender
Montoya API 规定,插件的入口类必须命名为 BurpExtender,并且实现 BurpExtension 接口。这个类只有一个 initialize 方法,Burpsuite 在加载插件时会调用它。
在 src/main/java 下创建你的包路径(如 com.security.plugin),然后新建一个名为 BurpExtender 的 Java 类。
package com.security.plugin;
import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.logging.Logging;
public class BurpExtender implements BurpExtension {
private MontoyaApi montoyaApi;
private Logging logging;
@Override
public void initialize(MontoyaApi api) {
// 保存API实例,后续到处都要用
this.montoyaApi = api;
this.logging = api.logging();
// 设置插件名称,这会在Burp的Extender面板显示
api.extension().setName("My Crypto Plugin");
// 输出日志,证明插件加载成功!
logging.logToOutput("=== 我的加解密插件初始化成功! ===");
// 在这里注册我们后续要写的各种 Handler
// 例如:api.proxy().registerRequestHandler(new MyProxyRequestHandler(api));
// 我们先注释掉,等Handler写完再取消注释
}
}
现在,一个最基础的插件框架就完成了。你可以点击 IDEA 的 Maven 面板中的 package 命令(或运行 mvn clean compile package),它会在 target 目录下生成一个 .jar 文件。
3.4 在 Burpsuite 中加载并测试
打开你的 Burpsuite,转到 Extender 标签页,再切换到 Extensions 子标签。点击 Add 按钮,在弹窗里选择 Java 类型,然后找到并选中你刚刚打包生成的 .jar 文件。点击 Next,如果一切顺利,你会在下方的输出面板看到 “Extension loaded successfully” 以及我们代码里打印的 “=== 我的加解密插件初始化成功! ===” 日志。
恭喜!至此,你的开发环境、项目框架和 Burpsuite 的联动已经完全跑通。这是万里长征第一步,也是最容易出问题的一步。如果这里成功了,后面就都是“添砖加瓦”的活了。
4. 集成加解密算法:让插件拥有“核心能力”
插件架子搭好了,现在要给它注入灵魂——加解密算法。通常我们需要从目标应用中逆向出它的加密逻辑。这里我以最常见的 AES-ECB 和 RSA 为例,演示如何将算法代码集成到插件项目中。
4.1 逆向与分析加密逻辑
在开发前,你需要弄清楚目标应用用了什么算法、密钥是什么、数据格式如何。通常有两种方法:
- 静态分析:如果能有前端代码,直接看 JavaScript 加密函数。用 VS Code 打开 JS 文件,搜索
CryptoJS、AES、encrypt、RSA等关键字。 - 动态调试:在浏览器开发者工具的
Sources或Debugger面板中,给加密函数打上断点,观察输入输出。
假设我们分析出以下逻辑(这是一个非常典型的场景):
- 请求体加密:POST 的 JSON 数据整体先经过 AES-ECB 模式加密,密钥是固定的字符串(如
this_is_a_secret_key)。 - 密钥传递:这个 AES 密钥本身,会被当前时间戳拼接后,再用 RSA 公钥加密,结果放在 HTTP 请求头的
X-Encrypt-Key字段里。 - 响应体加密:服务器返回的 JSON 数据中,
data字段是经过相同 AES 算法加密的密文。
4.2 实现 AES-ECB 加解密工具类
在项目中创建一个工具类,比如 AesUtil.java。这里给出一个健壮性更好的实现:
package com.security.plugin.utils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesUtil {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
/**
* AES-ECB 加密
* @param plainText 明文
* @param secretKey 密钥 (必须是16, 24或32字节)
* @return Base64编码的密文
*/
public static String encrypt(String plainText, String secretKey) {
try {
// 确保密钥长度符合AES要求,这里简单处理,实际可能需要补位
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("AES加密失败", e);
}
}
/**
* AES-ECB 解密
* @param cipherText Base64编码的密文
* @param secretKey 密钥
* @return 明文
*/
public static String decrypt(String cipherText, String secretKey) {
try {
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES解密失败", e);
}
}
// 一个简单的测试方法
public static void main(String[] args) {
String key = "1234567890123456"; // 16位密钥
String text = "{\"user\":\"admin\",\"pass\":\"123456\"}";
String encrypted = encrypt(text, key);
System.out.println("加密后: " + encrypted);
String decrypted = decrypt(encrypted, key);
System.out.println("解密后: " + decrypted);
System.out.println("解密是否成功: " + text.equals(decrypted));
}
}
4.3 实现 RSA 加解密工具类
再创建一个 RsaUtil.java。注意,在实际插件中,RSA 可能只用于加密密钥,解密可能在服务端。我们这里实现公钥加密和私钥解密(如果需要的话)。
package com.security.plugin.utils;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RsaUtil {
private static final String ALGORITHM = "RSA";
/**
* 从字符串加载RSA公钥
* @param publicKeyStr Base64编码的公钥字符串(通常去头尾)
* @return PublicKey 对象
*/
public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(spec);
}
/**
* 用公钥加密数据
* @param data 待加密数据
* @param publicKey 公钥字符串
* @return Base64编码的密文
*/
public static String encryptWithPublicKey(String data, String publicKey) throws Exception {
PublicKey key = loadPublicKey(publicKey);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 如果需要私钥解密,可以类似地实现 loadPrivateKey 和 decryptWithPrivateKey 方法
// ...
public static void main(String[] args) throws Exception {
// 这里需要替换成你从目标应用逆向出来的真实公钥
String samplePublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // 省略
String dataToEncrypt = "this_is_a_secret_key_" + System.currentTimeMillis();
String encrypted = encryptWithPublicKey(dataToEncrypt, samplePublicKey);
System.out.println("RSA加密结果: " + encrypted);
}
}
把这两个工具类写好,并在 main 方法里用你知道的密钥和密文测试通过,确保算法逻辑和你的目标应用完全一致。这是插件能否正常工作的基石。
5. 构建插件处理链:连接算法与 Burpsuite
现在我们有了解密算法,也知道 Burpsuite 的流量节点,接下来就是最核心的一步:编写各个 Handler,在正确的时机调用我们的算法。
5.1 设计配置界面(Swing UI)
为了让插件更实用,我们通常需要一个设置界面,让用户能动态输入 AES 密钥、RSA 公钥、配置主机白名单等。这里我们创建一个简单的 SettingsTab 类,实现 UserInterface 相关的接口。
package com.security.plugin.ui;
import burp.api.montoya.userinterface.UserInterface;
import javax.swing.*;
import java.awt.*;
public class SettingsTab implements burp.api.montoya.ui.UserInterface {
private final JPanel mainPanel;
private JTextField aesKeyField;
private JTextField rsaPubKeyField;
private JTextArea hostWhiteListArea;
public SettingsTab() {
mainPanel = new JPanel(new BorderLayout());
// 创建设置面板
JPanel configPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);
// AES密钥输入
gbc.gridx = 0; gbc.gridy = 0;
configPanel.add(new JLabel("AES密钥:"), gbc);
gbc.gridx = 1;
aesKeyField = new JTextField(30);
aesKeyField.setText("your_aes_key_here"); // 默认值
configPanel.add(aesKeyField, gbc);
// RSA公钥输入
gbc.gridx = 0; gbc.gridy = 1;
configPanel.add(new JLabel("RSA公钥:"), gbc);
gbc.gridx = 1;
rsaPubKeyField = new JTextField(30);
rsaPubKeyField.setText("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...");
configPanel.add(rsaPubKeyField, gbc);
// 主机白名单
gbc.gridx = 0; gbc.gridy = 2;
configPanel.add(new JLabel("主机白名单 (每行一个):"), gbc);
gbc.gridx = 1; gbc.gridy = 2;
gbc.gridheight = 3;
hostWhiteListArea = new JTextArea(10, 30);
hostWhiteListArea.setText("api.target.com\napp.victim.com");
configPanel.add(new JScrollPane(hostWhiteListArea), gbc);
mainPanel.add(configPanel, BorderLayout.NORTH);
// 可以添加一个保存按钮,将配置保存到持久化存储中
}
@Override
public Component getUiComponent() {
return mainPanel;
}
// 提供获取配置值的方法,供其他Handler调用
public String getAesKey() {
return aesKeyField.getText().trim();
}
public String getRsaPublicKey() {
return rsaPubKeyField.getText().trim();
}
public String[] getHostWhiteList() {
return hostWhiteListArea.getText().split("\\n");
}
}
然后在 BurpExtender 的 initialize 方法中注册这个标签页:api.userInterface().registerSuiteTab("加解密设置", settingsTab.getUiComponent());。
5.2 实现 MyProxyRequestHandler(请求解密)
这个 Handler 负责处理来自浏览器的请求。我们主要工作在 handleRequestToBeSent 方法中。
package com.security.plugin.handler;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.proxy.ProxyRequestHandler;
import burp.api.montoya.proxy.ProxyRequestReceivedAction;
import burp.api.montoya.proxy.ProxyRequestToBeSentAction;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.proxy.http.ProxyRequestReceived;
import burp.api.montoya.proxy.http.ProxyRequestToBeSent;
import com.security.plugin.ui.SettingsTab;
import com.security.plugin.utils.AesUtil;
import static burp.api.montoya.proxy.ProxyRequestReceivedAction.continueWith;
import static burp.api.montoya.proxy.ProxyRequestToBeSentAction.continueWith;
public class MyProxyRequestHandler implements ProxyRequestHandler {
private final MontoyaApi api;
private final SettingsTab settings;
public MyProxyRequestHandler(MontoyaApi api, SettingsTab settings) {
this.api = api;
this.settings = settings;
}
@Override
public ProxyRequestReceivedAction handleRequestReceived(ProxyRequestReceived requestReceived) {
// 1. 检查主机是否在白名单内,如果不是,直接放行,不处理
String host = requestReceived.request().host();
if (!isHostInWhiteList(host)) {
return continueWith(requestReceived);
}
// 2. 可以在这里记录原始请求的ID或提取一些信息,存入上下文
// api.logging().logToOutput("收到请求: " + requestReceived.request().url());
return continueWith(requestReceived);
}
@Override
public ProxyRequestToBeSentAction handleRequestToBeSent(ProxyRequestToBeSent requestToBeSent) {
String host = requestToBeSent.request().host();
if (!isHostInWhiteList(host)) {
return continueWith(requestToBeSent);
}
// 获取当前配置的AES密钥
String aesKey = settings.getAesKey();
if (aesKey.isEmpty()) {
api.logging().logToError("AES密钥未配置,跳过解密。");
return continueWith(requestToBeSent);
}
try {
// 获取请求体(假设是加密的)
ByteArray requestBody = requestToBeSent.request().body();
String bodyStr = requestBody.toString();
// 尝试解密请求体
String decryptedBody = AesUtil.decrypt(bodyStr, aesKey);
// 构建新的请求,用解密后的明文替换原请求体
ProxyRequestToBeSent newRequest = requestToBeSent.withBody(ByteArray.byteArray(decryptedBody));
// 记录日志,方便调试
api.logging().logToOutput(String.format("[ProxyReqOut] 主机: %s, 解密成功。明文: %s", host, decryptedBody));
// 继续发送修改后的请求
return continueWith(newRequest);
} catch (Exception e) {
// 如果解密失败(可能不是加密请求,或者密钥不对),则原样发送
api.logging().logToError(String.format("[ProxyReqOut] 主机: %s, 解密失败: %s", host, e.getMessage()));
return continueWith(requestToBeSent);
}
}
private boolean isHostInWhiteList(String host) {
for (String whiteHost : settings.getHostWhiteList()) {
if (host.contains(whiteHost.trim())) {
return true;
}
}
return false;
}
}
5.3 实现 MyHttpHandler(请求加密与响应解密)
这个 Handler 对于 Repeater 等工具发出的请求至关重要。它需要把明文重新加密发出去,并把加密响应解密回来。
package com.security.plugin.handler;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.core.ByteArray;
import com.security.plugin.ui.SettingsTab;
import com.security.plugin.utils.AesUtil;
import com.security.plugin.utils.RsaUtil;
import com.alibaba.fastjson2.JSONObject;
import java.util.Optional;
import static burp.api.montoya.http.handler.HttpRequestToBeSentAction.continueWith;
import static burp.api.montoya.http.handler.HttpResponseReceivedAction.continueWith;
public class MyHttpHandler implements HttpHandler {
private final MontoyaApi api;
private final SettingsTab settings;
public MyHttpHandler(MontoyaApi api, SettingsTab settings) {
this.api = api;
this.settings = settings;
}
@Override
public HttpRequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
String host = requestToBeSent.host();
if (!isHostInWhiteList(host)) {
return continueWith(requestToBeSent);
}
String aesKey = settings.getAesKey();
String rsaPubKey = settings.getRsaPublicKey();
try {
// 1. 获取请求体(此时应该是用户在Repeater里输入的明文)
ByteArray requestBody = requestToBeSent.body();
String plainBody = requestBody.toString();
// 2. 用AES加密请求体
String encryptedBody = AesUtil.encrypt(plainBody, aesKey);
// 3. 构造新的RSA加密的密钥头(模拟前端行为)
String timestamp = String.valueOf(System.currentTimeMillis());
String keyToEncrypt = aesKey + "_" + timestamp;
String encryptedKeyHeader = RsaUtil.encryptWithPublicKey(keyToEncrypt, rsaPubKey);
// 4. 构建新的请求:更新请求体和请求头
HttpRequestToBeSent newRequest = requestToBeSent
.withBody(ByteArray.byteArray(encryptedBody))
.withUpdatedHeader("X-Encrypt-Key", encryptedKeyHeader);
api.logging().logToOutput(String.format("[HttpReqOut] 主机: %s, 加密完成并添加签名头。", host));
return continueWith(newRequest);
} catch (Exception e) {
api.logging().logToError(String.format("[HttpReqOut] 主机: %s, 请求加密失败: %s", host, e.getMessage()));
return continueWith(requestToBeSent);
}
}
@Override
public HttpResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
String host = responseReceived.initiatingRequest().host();
if (!isHostInWhiteList(host)) {
return continueWith(responseReceived);
}
String aesKey = settings.getAesKey();
try {
ByteArray responseBody = responseReceived.body();
String bodyStr = responseBody.toString();
// 假设响应是JSON,且加密数据在 `data` 字段
JSONObject jsonResponse = JSONObject.parseObject(bodyStr);
Optional<String> encryptedDataOpt = Optional.ofNullable(jsonResponse.getString("data"));
if (encryptedDataOpt.isPresent() && !encryptedDataOpt.get().isEmpty()) {
// 解密 data 字段
String decryptedData = AesUtil.decrypt(encryptedDataOpt.get(), aesKey);
// 将解密后的数据替换回JSON
jsonResponse.put("data", decryptedData);
// 构建新的响应
HttpResponseReceived newResponse = responseReceived.withBody(ByteArray.byteArray(jsonResponse.toJSONString()));
api.logging().logToOutput(String.format("[HttpRespIn] 主机: %s, 响应解密成功。", host));
return continueWith(newResponse);
} else {
// 如果没有加密的data字段,可能是其他响应,原样返回
return continueWith(responseReceived);
}
} catch (Exception e) {
api.logging().logToError(String.format("[HttpRespIn] 主机: %s, 响应解密失败: %s", host, e.getMessage()));
return continueWith(responseReceived);
}
}
private boolean isHostInWhiteList(String host) {
// ... 同 MyProxyRequestHandler 中的实现
return true; // 简化示例
}
}
5.4 组装并注册 Handler
最后,回到 BurpExtender 类,在 initialize 方法中创建各个 Handler 实例并进行注册。
@Override
public void initialize(MontoyaApi api) {
this.montoyaApi = api;
this.logging = api.logging();
api.extension().setName("My Crypto Plugin V1.0");
logging.logToOutput("=== 插件初始化开始 ===");
// 1. 创建UI配置标签页
SettingsTab settingsTab = new SettingsTab();
api.userInterface().registerSuiteTab("加解密配置", settingsTab.getUiComponent());
// 2. 创建各个Handler,并传入api和配置页引用
MyProxyRequestHandler proxyReqHandler = new MyProxyRequestHandler(api, settingsTab);
MyHttpHandler httpHandler = new HttpHandler(api, settingsTab);
// 如果需要处理返回给浏览器的响应,还可以创建 MyProxyResponseHandler
// 3. 向Burpsuite注册Handler
api.proxy().registerRequestHandler(proxyReqHandler);
api.http().registerHttpHandler(httpHandler);
// api.proxy().registerResponseHandler(proxyRespHandler);
logging.logToOutput("=== 所有处理器注册完成,插件已就绪! ===");
}
6. 打包、调试与实战技巧
代码写完了,但离一个稳定可用的插件还有一段距离。打包和调试过程中有很多细节需要注意。
6.1 使用 Maven 生成可加载的 JAR
我们的 pom.xml 需要配置 maven-shade-plugin 或 maven-assembly-plugin 来打包所有依赖(除了 scope 为 provided 的 Montoya API)。这里推荐使用 maven-shade-plugin,它可以处理依赖冲突和重命名。
在 pom.xml 的 <build><plugins> 部分添加:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- 合并META-INF下的文件,避免冲突 -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- 指定主类,虽然不是必须,但规范 -->
<mainClass>com.security.plugin.BurpExtender</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- 排除签名文件,避免安全警告 -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
然后在 IDEA 中运行 mvn clean package,会在 target 目录下生成一个 -shaded.jar 文件,这个就是最终可以加载的插件包。
6.2 调试插件:日志与断点
调试是开发过程中最耗时的部分。有几个高效的方法:
- 充分利用日志:在所有关键判断、成功失败处都加上
api.logging().logToOutput()和logToError()。Burpsuite 的Extender -> Output和Errors标签页会实时显示这些信息,是定位问题的第一现场。 - 在 IDEA 中远程调试:这是最强大的手段。首先,在 IDEA 中为你的插件项目配置一个 “Remote JVM Debug” 运行配置,使用默认端口(如 5005)。然后,在启动 Burpsuite 时,修改启动脚本,加上 JVM 调试参数,例如:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar burpsuite_community.jar。这样启动 Burpsuite 后,在 IDEA 里点击 Debug,就能连接到 Burpsuite 的 JVM,在你插件的代码里打上断点,进行单步调试,观察变量状态。 - 使用 Repeater 作为测试工具:Repeater 是你最好的测试场。发送一个明文请求,观察插件是否将其加密;发送一个加密请求,观察 Proxy 历史或 Repeater 本身是否显示为明文。通过对比原始请求和最终发送的请求,可以快速定位问题出在哪个 Handler。
6.3 处理常见的坑与边界情况
在实际开发中,我遇到过不少坑,这里分享几个:
- 字符编码问题:确保加解密和字符串转换时始终使用
UTF-8。ByteArray的toString()默认也是 UTF-8,但显式指定更安全。 - Base64 格式:注意 URL Safe 与标准 Base64 的区别。有些前端可能使用
-和_替换+和/,并去掉填充=。你的工具类可能需要兼容处理。 - JSON 格式容错:解析请求/响应 JSON 时,不要假设结构永远固定。使用
Optional或判空来避免NullPointerException。 - 性能考虑:加解密是 CPU 密集型操作。在白名单判断时尽早返回,避免对非目标流量进行不必要的加解密尝试。
- 多线程安全:Burpsuite 的 Handler 可能在多线程环境下被调用。如果你的插件有共享状态(比如一个缓存),需要考虑同步问题。不过,像从 UI 读取配置这种操作,通常是线程安全的。
开发这样一个插件,从分析到最终稳定运行,可能需要反复迭代好几次。但一旦成功,它对你测试效率的提升是巨大的。你不再需要手动复制密文到外部工具解密,也不再需要担心重放攻击时忘记加密。一切都在 Burpsuite 内部自动化完成,让你可以完全专注于业务逻辑的安全测试本身。
237



被折叠的 条评论
为什么被折叠?



