【BurpSuite 插件开发】实战篇(六)实现自定义请求头的修改与请求测试

发布于:2025-08-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

1 前言

当你在测试一个网站的权限控制时,可能需要反复修改请求中的Cookie、User-Agent等信息,观察不同身份下服务器的响应。如果每次都手动修改,不仅效率低下,还容易出错。而我们今天要实现插件的部分功能,能帮你自动完成这些重复工作。

本文继续该系列的插件开发,从添加一个“开始测试”按钮,到如何让插件正确修改请求头,再到如何通过右键菜单快速使用插件功能。

最后优化之前章节关于插件右键菜单项的逻辑处理。

文章结尾查看完整代码。

2 最终效果

在这里插入图片描述

3 添加启动测试按钮

修改 createConfigPanel 类,这段代码完成了以下工作:

  1. 创建一个名为"开始测试"的按钮
  2. 设置按钮的对齐方式为左对齐
  3. 设置按钮边框,增加按钮周围的空白区域,使按钮看起来更美观
  4. 为按钮添加点击事件监听器,当用户点击按钮时会调用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 替换请求头并发送

这段代码是测试功能的入口点,主要完成以下工作:

  1. 记录日志,标记测试开始
  2. 获取并验证用户输入的临时请求头
  3. 解析请求头内容,将每行转换为列表
  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();
}

后台测试执行逻辑

这个后台执行部分的逻辑如下:

  1. 遍历所有原始请求
  2. 对每个请求调用modifyRequestHeaders方法修改请求头
  3. 使用Burp的API发送修改后的请求
  4. 保存响应结果
  5. 计算并更新响应长度到表格中
@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;
}

请求头修改逻辑

这个方法负责修改请求头,具体步骤:

  1. 将临时请求头分为两类:需要更新的(原请求中已存在)和需要添加的(原请求中不存在)
  2. 遍历所有临时请求头,根据是否在原请求中存在进行分类
  3. 使用Burp API的withUpdatedHeaders方法更新已存在的头部
  4. 使用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

测试完成后会执行以下操作:

  1. 检查是否有异常并记录日志
  2. 更新UI显示,如果当前有选中的行就显示该行的请求响应信息
  3. 如果没有选中行但有请求数据,则默认显示第一行
@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 插件面板工作流程

  1. 用户通过右键菜单将请求发送到PermissionTest插件面板
  2. 用户在"配置中心"的临时请求头区域输入要修改的请求头(如Cookie)
  3. 点击"开始测试"按钮触发测试流程
  4. 程序在后台线程中对每个请求进行处理:
    • 根据用户输入修改请求头
    • 发送修改后的请求
    • 接收响应并计算响应长度
    • 更新表格中的数据
  5. 测试完成后在UI上显示结果

6 优化按钮菜单逻辑

定义菜单项枚举类

MenuItemType区分请求和Cookie两种菜单项类型。

private enum MenuItemType {
    REQUEST,
    COOKIE
}

菜单项提供方法

这个方法是ContextMenuItemsProvider接口的核心方法,负责创建并返回菜单项列表:

  1. 创建一个Component列表用于存放菜单项。
  2. 使用createMenuItem方法创建两个菜单项:
    • “Send Request to PermissionTest”(发送请求到插件面板)
    • “Send Cookie to PermissionTest”(发送Cookie到插件面板)
  3. 将创建的菜单项添加到列表中并返回。
@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;
}

菜单项创建方法

这个方法负责创建单个菜单项并设置其行为:

  1. 创建具有指定文本的JMenuItem
  2. 为菜单项添加动作监听器
  3. 在动作监听器中处理菜单项点击事件

根据菜单项类型(REQUEST或COOKIE)执行不同操作:

  1. REQUEST类型:将选中的请求添加到PermissionTest面板的表格中
  2. COOKIE类型:提取选中请求中的Cookie头并设置到PermissionTest面板的临时请求头区域

Cookie处理优化特点

  1. 去重处理:
    • 使用LinkedHashMap自动处理重复的Cookie名称
    • 后面的Cookie值会覆盖前面的同名Cookie值,符合浏览器行为
  2. 保留不同键值对:
    • 所有不同名称的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 插件菜单功能流程

  1. 用户在Burp Suite中右键点击HTTP消息
  2. 插件提供两个菜单项:
    • “Send Request to PermissionTest”:发送整个请求到插件
    • “Send Cookie to PermissionTest”:仅提取并发送Cookie到插件
  3. 用户选择相应菜单项后:
    • REQUEST类型:将选中请求添加到插件的请求列表表格中
    • COOKIE类型:提取Cookie并填充到插件的临时请求头输入框中
  4. 插件记录操作日志

8 章节总结

到这里,我们已经完整讲解了“根据自定义请求头修改原始请求”插件的核心功能。总结一下,这个插件能帮你解决以下问题:

  • 效率提升:无需手动逐个修改请求头,批量处理多个请求,适合测试大量相似请求的场景。
  • 操作简化:通过右键菜单快速导入请求或Cookie,减少复制粘贴的麻烦。
  • 安全测试辅助:轻松测试权限控制、请求头注入等漏洞,比如通过修改Cookie、User-Agent等头部观察响应变化。

对于安全小白来说,这个插件不仅是一个实用工具,更是学习Burp插件开发的好例子——它涵盖了UI设计、事件处理、后台线程、Burp API调用等核心知识点。


本插件代码开源地址:Gitee代码仓