1 前言
历史章节:
【BurpSuite 2025最新版插件开发】基础篇1:环境搭建
【BurpSuite 2025最新版插件开发】基础篇2:插件生命周期与核心接口
【BurpSuite 2025最新版插件开发】基础篇3:请求拦截和修改简单示例
【BurpSuite 2025最新版插件开发】基础篇4:HTTP流量处理
【BurpSuite 2025最新版插件开发】基础篇5:UI组件开发
【BurpSuite 2025最新版插件开发】基础篇6:UI组件与数据传输
本章节注意讲解 BurpSuite 插件中对于数据持久化存储的基本使用。
2 接口简介
2.1 PersistedList<T>
:项目级列表存储
- 功能定位
存储有序的、可重复的数据列表,支持泛型类型(如字符串、整数等),数据随Burp项目文件保存。 - 核心特性
- 类型安全:通过泛型约束元素类型(如
PersistedList<String>
)。 - 项目隔离:每个项目独立保存列表数据,切换项目时自动加载对应数据。
- 动态操作:支持
add
、remove
、size
等列表操作,适合存储可变数据集合(如URL历史记录)。
- 类型安全:通过泛型约束元素类型(如
- 使用场景
存储插件运行时动态生成的列表数据(如扫描结果、自定义Payload列表)。
2.2 PersistedObject
:项目级复杂对象存储(仅供专业版)
- 功能定位
存储单个结构化对象(如配置类、自定义数据模型),需实现java.io.Serializable
接口。 - 核心特性
- 复杂数据支持:可存储嵌套对象、集合等复杂结构。
- 项目绑定:数据与项目关联,适合保存项目特定的配置或状态。
- 版本兼容性:需注意序列化版本UID,避免跨版本兼容性问题。
- 使用场景
存储插件的完整配置(如多模块扫描策略)或复杂状态数据。
2.3 Persistence
:持久化功能入口
- 功能定位
工厂接口,用于创建和管理PersistedList
与PersistedObject
实例。 - 核心方法
createPersistedList(String name, Class<T> elementType)
:创建命名列表。persistedObject(String name)
:获取或创建命名对象。
- 使用场景
插件初始化时通过montoya.persistence()
获取实例,再创建具体持久化存储。
2.4 Preferences
:用户级全局配置
- 功能定位
存储与项目无关的用户偏好或全局设置(如API密钥、界面主题)。 - 核心特性
- 跨项目共享:数据存储在用户配置文件中,所有项目均可访问。
- 基本类型支持:仅支持字符串、布尔值、整数等简单类型。
- 监听机制:可注册监听器实时响应配置变化。
- 使用场景
存储插件的全局设置(如默认扫描选项、认证凭证)。
2.5 持久化接口对比与选择建议
接口名称 | PersistedList | PersistedObject | Persistence | Preferences |
---|---|---|---|---|
功能定位 | 存储有序、可重复的数据列表,支持泛型类型。 | 存储单个结构化对象(如配置类、数据模型)。 | 工厂接口,用于创建和管理其他持久化接口。 | 存储用户级全局配置(与项目无关)。 |
数据作用域 | 项目级(随项目文件保存)。 | 项目级(随项目文件保存)。 | - | 用户级(跨项目共享)。 |
支持数据类型 | 泛型列表(需指定元素类型)。 | 内置类型(如HTTP请求/响应)、自定义对象。 | - | 基本类型(String、Boolean、Integer等)。 |
序列化要求 | 元素需实现 Serializable (如自定义类)。 |
文档未明确要求,但自定义对象可能需适配。 | - | 无需序列化。 |
核心方法 | add , remove , get , size , clear |
set , get , setChildObject , remove |
createPersistedList , persistedObject |
put , get , remove , addListener |
典型场景 | 存储动态生成的项目相关列表(如URL历史)。 | 存储复杂配置或项目状态(如多模块策略)。 | 插件初始化时获取持久化功能入口。 | 存储全局用户偏好(如API密钥、主题)。 |
版本限制 | 社区版/专业版均可使用。 | 专业版(标注 [Professional only] )。 |
社区版/专业版均可使用。 | 社区版/专业版均可使用。 |
关键说明
PersistedList
适合动态增删的列表场景,但查询效率依赖列表长度(O(n))。PersistedObject
支持复杂数据结构,但需注意专业版限制及可能的序列化适配。Persistence
是创建其他持久化对象的唯一入口,需通过montoya.persistence()
获取。Preferences
适合存储与项目无关的配置,数据跨项目共享且无需序列化。
3 实战模拟
插件实现如下需求:
- 通过 HTTP 接口获取不同类型用户的信息:token、uid、role 后进行持久化存储。
- 在业务接口中使用不同类型用户的信息调用业务接口,来测试业务接口的权限隔离。
- 使用 BurpSuite 版本通用的
PersistedList
接口实现数据持久化。
4 代码示例
4.1 服务端代码
使用 python 代码实现极简服务端,实现如下需求:
- 预先定义了系统固定的用户和管理员信息,包含 token、uid 和 role 三个字段。
- 实现 3 个接口:
获取用户信息接口(/user_info)
- 请求方法:GET
- 功能:返回预先定义的用户信息,包含 token、uid 和 role。
- 响应示例:
获取管理员信息接口(/admin_info)
- 请求方法:GET
- 功能:返回预先定义的管理员信息,包含 token、uid 和 role。
- 响应示例:
添加用户接口(/add_user)
请求方法:POST
请求参数:以 JSON 格式传递 token、uid 和 role 三个参数。
功能:检查传入的参数是否与预先定义的管理员信息一致,若一致则返回添加成功的消息;若不一致则返回添加失败的消息。
成功响应示例:
失败响应示例:
服务端 python 代码:
from flask import Flask, request, jsonify
app = Flask(__name__)
# 模拟用户和管理员信息
USER_INFO = {
"token": "user_token_123",
"uid": "user_uid_123",
"role": "user"
}
ADMIN_INFO = {
"token": "admin_token_123",
"uid": "admin_uid_123",
"role": "admin"
}
# 获取用户信息接口
@app.route('/user_info', methods=['GET'])
def get_user_info():
return jsonify(USER_INFO)
# 获取管理员信息接口
@app.route('/admin_info', methods=['GET'])
def get_admin_info():
return jsonify(ADMIN_INFO)
# 添加用户接口
@app.route('/add_user', methods=['POST'])
def add_user():
data = request.get_json()
if not data or 'token' not in data or 'uid' not in data or 'role' not in data:
return jsonify({"message": "Missing parameters"}), 400
if data['token'] == ADMIN_INFO['token'] and data['uid'] == ADMIN_INFO['uid'] and data['role'] == ADMIN_INFO['role']:
return jsonify({"message": "User added successfully"}), 200
else:
return jsonify({"message": "Adding user failed"}), 400
if __name__ == '__main__':
app.run(debug=True)
通过命令行启动:
4.2 插件代码
实现需求:
- 拦截 HTTP 响应、持久化存储用户数据。
- 通过右键菜单触发的业务接口的测试请求。
- 测试请求分别使用 user 和 admin 的信息请求
/add_user
接口。
- Extension.java
- 插件的主类,实现了
BurpExtension
接口,设置插件名称。 - 创建两个
PersistedList<String>
实例,用于持久化存储用户信息和管理员信息。 - 注册
CustomHttpHandler
来处理 HTTP 请求/响应。 - 注册
TestMenu
右键菜单项。
- 插件的主类,实现了
import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.persistence.PersistedList;
@SuppressWarnings("unused")
public class Extension implements BurpExtension {
@Override
public void initialize(MontoyaApi montoyaApi) {
montoyaApi.extension().setName("My Extension");
// 创建持久化对象
PersistedList<String> userInfoList = PersistedList.persistedStringList();
PersistedList<String> adminInfoList = PersistedList.persistedStringList();
// HTTP 监听器
montoyaApi.http().registerHttpHandler(new CustomHttpHandler(montoyaApi, userInfoList, adminInfoList));
// 注册右键-测试请求
montoyaApi.userInterface().registerContextMenuItemsProvider(new TestMenu(montoyaApi, userInfoList, adminInfoList));
}
}
- CustomHttpHandler.java
- 该类实现了
HttpHandler
接口,用于监听和处理 HTTP 请求与响应。 - 在
handleHttpResponseReceived
方法中,检查响应内容是否包含"role": "user"
或"role": "admin"
,并分别将这些信息添加到userInfoList
和adminInfoList
中。
- 该类实现了
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.persistence.PersistedList;
public class CustomHttpHandler implements HttpHandler {
private final MontoyaApi montoyaApi;
private final PersistedList<String> userInfoList;
private final PersistedList<String> adminInfoList;
public CustomHttpHandler(MontoyaApi montoyaApi, PersistedList<String> userInfoList, PersistedList<String> adminInfoList) {
this.montoyaApi = montoyaApi;
this.userInfoList = userInfoList;
this.adminInfoList = adminInfoList;
}
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) {
return RequestToBeSentAction.continueWith(httpRequestToBeSent);
}
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
// 持久化存储
if (httpResponseReceived.bodyToString().contains("\"role\": \"user\"")) {
userInfoList.add(httpResponseReceived.body().toString());
String userValue = userInfoList.stream().findFirst().orElse("无用户信息");
montoyaApi.logging().logToOutput("获取 UserInfoList: " + userValue);
}
if (httpResponseReceived.bodyToString().contains("\"role\": \"admin\"")) {
adminInfoList.add(httpResponseReceived.body().toString());
String adminValue = adminInfoList.stream().findFirst().orElse("无管理员信息");
montoyaApi.logging().logToOutput("获取 AdminInfoList: " + adminValue);
}
// 打印响应
montoyaApi.logging().logToOutput("响应体: " + httpResponseReceived.bodyToString());
return ResponseReceivedAction.continueWith(httpResponseReceived);
}
}
- TestMenu.java
- 该类实现了
ContextMenuItemsProvider
接口,为 Burp 的上下文菜单提供自定义菜单项。 - 提供一个名为“测试请求”的菜单项;点击菜单项后会调用
MyRequest.sendTestRequest(...)
方法,传递userInfoList
和adminInfoList
。 - 使用事件监听机制来触发请求。
- 该类实现了
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.persistence.PersistedList;
import burp.api.montoya.ui.contextmenu.ContextMenuEvent;
import burp.api.montoya.ui.contextmenu.ContextMenuItemsProvider;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class TestMenu implements ContextMenuItemsProvider {
private final MontoyaApi montoyaApi;
private final PersistedList<String> userInfoList;
private final PersistedList<String> adminInfoList;
public TestMenu(MontoyaApi montoyaApi, PersistedList<String> userInfoList, PersistedList<String> adminInfoList) {
this.montoyaApi = montoyaApi;
this.userInfoList = userInfoList;
this.adminInfoList = adminInfoList;
}
@Override
public List<Component> provideMenuItems(ContextMenuEvent event) {
List<Component> menuItems = new ArrayList<>();
JMenuItem menuItem = new JMenuItem("测试请求");
menuItem.addActionListener(e -> {
MyRequest myRequest = new MyRequest(montoyaApi);
myRequest.sendTestRequest(userInfoList, adminInfoList);
});
menuItems.add(menuItem);
return menuItems;
}
}
- MyRequest.java
- 该类封装了发送 HTTP 请求的逻辑。
sendTestRequest(...)
方法从PersistedList<String>
中提取第一个用户信息和管理员信息;如果信息为空,则记录错误日志;- 使用
SwingWorker
异步执行网络操作,避免阻塞主线程; sendRequest(...)
方法解析 JSON 数据,并构造新的 HTTP POST 请求发送到服务器。
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.HttpService;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.persistence.PersistedList;
import burp.api.montoya.utilities.json.JsonUtils;
import javax.swing.*;
public class MyRequest {
private final MontoyaApi montoyaApi;
public MyRequest(MontoyaApi montoyaApi) {
this.montoyaApi = montoyaApi;
}
public void sendTestRequest(PersistedList<String> userInfoList, PersistedList<String> adminInfoList) {
String userValue = userInfoList.stream().findFirst().orElse("");
String adminValue = adminInfoList.stream().findFirst().orElse("");
if (userValue.isEmpty() || adminValue.isEmpty()) {
montoyaApi.logging().logToError("no user info or admin info");
return;
}
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
sendRequest(userInfoList, "user");
sendRequest(adminInfoList, "admin");
return null;
}
}.execute();
}
private void sendRequest(PersistedList<String> dataList, String roleType) {
try {
String infoValue = dataList.stream().findFirst().orElse(null);
if (infoValue == null) {
montoyaApi.logging().logToError("数据为空: " + roleType);
return;
}
JsonUtils jsonUtils = montoyaApi.utilities().jsonUtils();
String role = jsonUtils.readString(infoValue, "role");
String token = jsonUtils.readString(infoValue, "token");
String uid = jsonUtils.readString(infoValue, "uid");
String requestBody = String.format("{\"role\":\"%s\",\"token\":\"%s\",\"uid\":\"%s\"}", role, token, uid);
HttpService httpService = HttpService.httpService("localhost", 5000, false);
HttpRequest httpRequest = HttpRequest.httpRequest()
.withPath("/add_user")
.withService(httpService)
.withBody(requestBody)
.withHeader("Content-Type", "application/json")
.withMethod("POST");
montoyaApi.http().sendRequest(httpRequest);
} catch (Exception e) {
montoyaApi.logging().logToError("error:" + e.getMessage());
}
}
}
5 效果展示
5.1 抓包接口获取用户数据
获取 user 信息:
获取 admin 信息:
日志打印:
5.2 右键触发测试请求
触发请求 /add_user
接口:
日志打印:可以看到输出日志符合服务端的判断。
插件:
sendRequest(userInfoList, "user");
sendRequest(adminInfoList, "admin");
服务端:
if data['token'] == ADMIN_INFO['token'] and data['uid'] == ADMIN_INFO['uid'] and data['role'] == ADMIN_INFO['role']:
return jsonify({"message": "User added successfully"}), 200
else:
return jsonify({"message": "Adding user failed"}), 400
6 总结
整个插件的功能流程如下:
- 拦截响应:通过
CustomHttpHandler
类拦截 HTTP 响应,识别包含"role": "user"
或"role": "admin"
的响应体,并将其持久化存储到PersistedList
中; - 持久化存储:使用 Montoya API 的
PersistedList
来保存用户和管理员信息; - 触发测试请求:通过右键菜单项(由
TestMenu
类实现)触发测试请求; - 异步发送请求:在后台线程中使用
MyRequest
类发送包含用户和管理员信息的 HTTP 请求,避免阻塞 UI。
- 拦截响应:通过
本插件适用于需要从响应中提取特定信息并基于这些信息发起新请求的安全测试场景。
注:由于社区版不支持保存项目到本地,因此要最大程度的发挥persistence
的作用建议使用专业版。