文章目录
1 前言
当你在测试一个网站的权限控制时,可能需要反复修改请求中的Cookie、User-Agent等信息,观察不同身份下服务器的响应。如果每次都手动修改,不仅效率低下,还容易出错。而我们今天要实现插件的部分功能,能帮你自动完成这些重复工作。
本文继续该系列的插件开发,从添加一个“开始测试”按钮,到如何让插件正确修改请求头,再到如何通过右键菜单快速使用插件功能。
最后优化之前章节关于插件右键菜单项的逻辑处理。
文章结尾查看完整代码。
2 最终效果
3 添加启动测试按钮
修改 createConfigPanel
类,这段代码完成了以下工作:
- 创建一个名为"开始测试"的按钮
- 设置按钮的对齐方式为左对齐
- 设置按钮边框,增加按钮周围的空白区域,使按钮看起来更美观
- 为按钮添加点击事件监听器,当用户点击按钮时会调用performTest()方法执行测试
// 开始测试按钮
JButton startTestButton = new JButton("开始测试");
startTestButton.setAlignmentX(Component.LEFT_ALIGNMENT);
startTestButton.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// performTest 待实现
startTestButton.addActionListener(e -> performTest());
...
contentPanel.add(clearButton);
contentPanel.add(Box.createVerticalStrut(10)); // 添加间距
contentPanel.add(startTestButton);
contentPanel.add(Box.createVerticalStrut(10));
4 替换请求头并发送
这段代码是测试功能的入口点,主要完成以下工作:
- 记录日志,标记测试开始
- 获取并验证用户输入的临时请求头
- 解析请求头内容,将每行转换为列表
- 使用
SwingWorker
在后台线程执行测试,避免阻塞UI界面
// 开始测试
private void performTest() {
montoyaApi.logging().logToOutput("开始执行测试...");
// 获取临时请求头内容
String tempHeaders = headerTextArea.getText();
if (tempHeaders == null || tempHeaders.trim().isEmpty()) {
montoyaApi.logging().logToOutput("未设置临时请求头");
return;
}
// 解析临时请求头
List<String> headerLines = new ArrayList<>();
String[] lines = tempHeaders.split("\n");
for (String line : lines) {
if (!line.trim().isEmpty()) {
headerLines.add(line.trim());
}
}
// 使用SwingWorker执行测试,避免阻塞UI线程
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() {
// 实际测试逻辑
}
@Override
protected void done() {
// 测试完成后更新UI
}
};
// 启动后台任务
worker.execute();
}
后台测试执行逻辑
这个后台执行部分的逻辑如下:
- 遍历所有原始请求
- 对每个请求调用
modifyRequestHeaders
方法修改请求头 - 使用Burp的API发送修改后的请求
- 保存响应结果
- 计算并更新响应长度到表格中
@Override
protected Void doInBackground() {
// 对每个原始请求执行测试
for (int i = 0; i < requestResponseList.size(); i++) {
HttpRequestResponse originalRequestResponse = requestResponseList.get(i);
HttpRequest originalRequest = originalRequestResponse.request();
// 修改请求头
HttpRequest modifiedRequest = modifyRequestHeaders(originalRequest, headerLines);
// 发送修改后的请求
HttpRequestResponse modifiedRequestResponse = montoyaApi.http().sendRequest(modifiedRequest);
// 保存修改后的请求响应
while (modifiedRequestResponseList.size() <= i) {
modifiedRequestResponseList.add(null);
}
modifiedRequestResponseList.set(i, modifiedRequestResponse);
// 更新表格中的修改响应长度
if (modifiedRequestResponse.response() != null) {
HttpResponse response = modifiedRequestResponse.response();
String contentLength = response.headerValue("Content-Length");
String responseLength;
if (contentLength != null && !contentLength.isEmpty()) {
responseLength = contentLength;
} else {
responseLength = String.valueOf(response.toByteArray().length());
}
final int index = i;
final String length = responseLength;
// 在EDT中更新UI
SwingUtilities.invokeLater(() -> {
tableModel.setValueAt(length, index, 4); // 修改响应长度列
});
}
}
return null;
}
请求头修改逻辑
这个方法负责修改请求头,具体步骤:
- 将临时请求头分为两类:需要更新的(原请求中已存在)和需要添加的(原请求中不存在)
- 遍历所有临时请求头,根据是否在原请求中存在进行分类
- 使用Burp API的
withUpdatedHeaders
方法更新已存在的头部 - 使用
withAddedHeaders
方法添加新头部
// 修改请求头
private HttpRequest modifyRequestHeaders(HttpRequest originalRequest, List<String> tempHeaders) {
// 构建需要更新的请求头列表(仅包含原始请求中已存在的头部)
List<HttpHeader> updatedHeaders = new ArrayList<>();
// 构建需要添加的请求头列表(仅包含原始请求中不存在的头部)
List<HttpHeader> addedHeaders = new ArrayList<>();
// 分类临时请求头
for (String tempHeader : tempHeaders) {
String[] parts = tempHeader.split(":", 2);
if (parts.length >= 2) {
String headerName = parts[0].trim();
String headerValue = parts[1].trim();
HttpHeader header = HttpHeader.httpHeader(headerName, headerValue);
// 检查原始请求中是否已存在同名头部
boolean existsInOriginal = false;
for (HttpHeader originalHeader : originalRequest.headers()) {
if (originalHeader.name().equalsIgnoreCase(headerName)) {
existsInOriginal = true;
break;
}
}
if (existsInOriginal) {
updatedHeaders.add(header);
} else {
addedHeaders.add(header);
}
}
}
// 先使用withUpdatedHeaders更新已存在的头部
HttpRequest newRequest = originalRequest.withUpdatedHeaders(updatedHeaders);
// 再使用withAddedHeaders添加新的头部
newRequest = newRequest.withAddedHeaders(addedHeaders);
return newRequest;
}
测试完成后更新UI
测试完成后会执行以下操作:
- 检查是否有异常并记录日志
- 更新UI显示,如果当前有选中的行就显示该行的请求响应信息
- 如果没有选中行但有请求数据,则默认显示第一行
@Override
protected void done() {
try {
// 检查是否有异常
get();
montoyaApi.logging().logToOutput("测试执行完成");
} catch (Exception e) {
montoyaApi.logging().logToError("测试执行出错: " + e.getMessage());
}
// 如果当前选中了某行,则更新显示
int selectedRow = requestTable.getSelectedRow();
displayRequestResponse(selectedRow);
// 如果没有选中行但有请求,则默认显示第一行
if (selectedRow < 0 && !requestResponseList.isEmpty()) {
displayRequestResponse(0);
}
}
5 插件面板工作流程
- 用户通过右键菜单将请求发送到
PermissionTest
插件面板 - 用户在"配置中心"的临时请求头区域输入要修改的请求头(如Cookie)
- 点击"开始测试"按钮触发测试流程
- 程序在后台线程中对每个请求进行处理:
- 根据用户输入修改请求头
- 发送修改后的请求
- 接收响应并计算响应长度
- 更新表格中的数据
- 测试完成后在UI上显示结果
6 优化按钮菜单逻辑
定义菜单项枚举类
MenuItemType
区分请求和Cookie两种菜单项类型。
private enum MenuItemType {
REQUEST,
COOKIE
}
菜单项提供方法
这个方法是ContextMenuItemsProvider
接口的核心方法,负责创建并返回菜单项列表:
- 创建一个
Component
列表用于存放菜单项。 - 使用
createMenuItem
方法创建两个菜单项:- “Send Request to PermissionTest”(发送请求到插件面板)
- “Send Cookie to PermissionTest”(发送Cookie到插件面板)
- 将创建的菜单项添加到列表中并返回。
@Override
public List<Component> provideMenuItems(ContextMenuEvent event) {
// 创建菜单项
List<Component> menuItems = new ArrayList<>();
// 添加按钮"发送请求到插件面板"
JMenuItem requestToPermissionTest = createMenuItem("Send Request to PermissionTest", event, MenuItemType.REQUEST);
// 添加按钮"发送cookie到插件面板"
JMenuItem cookieToPermissionTest = createMenuItem("Send Cookie to PermissionTest", event, MenuItemType.COOKIE);
// 添加按钮到菜单项列表
menuItems.add(requestToPermissionTest);
menuItems.add(cookieToPermissionTest);
return menuItems;
}
菜单项创建方法
这个方法负责创建单个菜单项并设置其行为:
- 创建具有指定文本的
JMenuItem
- 为菜单项添加动作监听器
- 在动作监听器中处理菜单项点击事件
根据菜单项类型(REQUEST或COOKIE)执行不同操作:
- REQUEST类型:将选中的请求添加到
PermissionTest
面板的表格中 - COOKIE类型:提取选中请求中的Cookie头并设置到
PermissionTest
面板的临时请求头区域
Cookie处理优化特点
- 去重处理:
- 使用
LinkedHashMap
自动处理重复的Cookie名称 - 后面的Cookie值会覆盖前面的同名Cookie值,符合浏览器行为
- 使用
- 保留不同键值对:
- 所有不同名称的Cookie都会被保留
- 不会丢失任何Cookie信息
// 创建菜单项
private JMenuItem createMenuItem(String text, ContextMenuEvent event, MenuItemType type) {
JMenuItem menuItem = new JMenuItem(text);
menuItem.addActionListener(e -> {
List<HttpRequestResponse> selectedRequests = new ArrayList<>();
if (event.selectedRequestResponses() != null) {
selectedRequests.addAll(event.selectedRequestResponses());
}
if (!selectedRequests.isEmpty()) {
switch (type) {
case REQUEST:
permissionTestPanel.addRequestsToTable(selectedRequests);
montoyaApi.logging().logToOutput("Selected " + selectedRequests.size() + " requests.");
break;
case COOKIE:
// 清空临时请求头
permissionTestPanel.clearTempHeaderContent();
// 使用LinkedHashMap保持插入顺序并自动去重
Map<String, String> cookieMap = new LinkedHashMap<>();
// 遍历所有选中的请求
for (HttpRequestResponse requestResponse : selectedRequests) {
HttpRequest request = requestResponse.request();
if (request != null) {
// 获取所有的Cookie头
List<String> cookieHeaders = request.headers().stream()
.filter(header -> "Cookie".equalsIgnoreCase(header.name()))
.map(HttpHeader::value)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
// 解析每个Cookie头中的键值对
for (String cookieHeader : cookieHeaders) {
if (cookieHeader != null && !cookieHeader.isEmpty()) {
// 按分号分割Cookie键值对
String[] cookiePairs = cookieHeader.split(";");
for (String cookiePair : cookiePairs) {
String trimmedCookiePair = cookiePair.trim();
if (!trimmedCookiePair.isEmpty()) {
// 分割键和值
int equalsIndex = trimmedCookiePair.indexOf('=');
if (equalsIndex > 0) {
String cookieName = trimmedCookiePair.substring(0, equalsIndex).trim();
String cookieValue = trimmedCookiePair.substring(equalsIndex + 1).trim();
// 用新值覆盖旧值
cookieMap.put(cookieName, cookieValue);
} else {
// 没有值的Cookie
cookieMap.put(trimmedCookiePair, "");
}
}
}
}
}
}
}
// 构建最终的Cookie头
if (!cookieMap.isEmpty()) {
StringBuilder content = new StringBuilder();
content.append("Cookie: ");
boolean first = true;
for (java.util.Map.Entry<String, String> entry : cookieMap.entrySet()) {
if (!first) {
content.append("; ");
}
if (entry.getValue().isEmpty()) {
content.append(entry.getKey());
} else {
content.append(entry.getKey()).append("=").append(entry.getValue());
}
first = false;
}
permissionTestPanel.setTempHeaderContent(content.toString());
}
montoyaApi.logging().logToOutput("Sent " + selectedRequests.size() + " cookies to PermissionTest panel.");
break;
}
}
});
return menuItem;
}
7 插件菜单功能流程
- 用户在Burp Suite中右键点击HTTP消息
- 插件提供两个菜单项:
- “Send Request to PermissionTest”:发送整个请求到插件
- “Send Cookie to PermissionTest”:仅提取并发送Cookie到插件
- 用户选择相应菜单项后:
- REQUEST类型:将选中请求添加到插件的请求列表表格中
- COOKIE类型:提取Cookie并填充到插件的临时请求头输入框中
- 插件记录操作日志
8 章节总结
到这里,我们已经完整讲解了“根据自定义请求头修改原始请求”插件的核心功能。总结一下,这个插件能帮你解决以下问题:
- 效率提升:无需手动逐个修改请求头,批量处理多个请求,适合测试大量相似请求的场景。
- 操作简化:通过右键菜单快速导入请求或Cookie,减少复制粘贴的麻烦。
- 安全测试辅助:轻松测试权限控制、请求头注入等漏洞,比如通过修改Cookie、User-Agent等头部观察响应变化。
对于安全小白来说,这个插件不仅是一个实用工具,更是学习Burp插件开发的好例子——它涵盖了UI设计、事件处理、后台线程、Burp API调用等核心知识点。
本插件代码开源地址:Gitee代码仓