Burp Suite 擴展開發入門

Burp Suite Extension Development Getting Started

Burp Suite 擴展概述

Burp Suite 是滲透測試領域最廣泛使用的工具之一,其強大之處不僅在於內建功能,更在於其可擴展性。透過 Burp Suite Extender API,開發者可以建立自訂擴展來增強工具的功能,滿足特定的測試需求。

為什麼要開發 Burp Suite 擴展?

  • 自動化重複性任務:將常見的測試流程自動化
  • 客製化掃描規則:針對特定應用程式建立專屬的漏洞檢測規則
  • 整合第三方工具:將 Burp Suite 與其他安全工具串接
  • 擴展功能:新增 Burp Suite 原生不支援的功能

擴展類型

Burp Suite 擴展可以使用以下語言開發:

語言優點缺點
Java效能最佳、完整 API 支援開發較複雜
Python (Jython)開發快速、語法簡潔效能較差、需安裝 Jython
Ruby (JRuby)語法優雅使用者較少、社群資源有限

開發環境設定

Java 開發環境

1. 安裝 JDK

1
2
3
4
5
6
7
# Ubuntu/Debian
sudo apt update
sudo apt install openjdk-17-jdk

# 驗證安裝
java -version
javac -version

2. 設定 IDE

建議使用 IntelliJ IDEA 或 Eclipse。以 IntelliJ IDEA 為例:

1
2
3
4
5
# 下載 IntelliJ IDEA Community Edition
wget https://download.jetbrains.com/idea/ideaIC-2024.1.tar.gz
tar -xzf ideaIC-2024.1.tar.gz
cd idea-IC-*/bin
./idea.sh

3. 取得 Burp Suite API

Burp Suite API 可以從 Burp Suite 應用程式中匯出:

  1. 開啟 Burp Suite
  2. 前往 Extender > APIs
  3. 點擊「Save interface files」儲存 API 介面檔案

或者使用 Maven 依賴:

1
2
3
4
5
6
7
8
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>net.portswigger.burp.extender</groupId>
        <artifactId>burp-extender-api</artifactId>
        <version>2.3</version>
    </dependency>
</dependencies>

Python (Jython) 開發環境

1. 下載並安裝 Jython

1
2
3
4
5
# 下載 Jython standalone JAR
wget https://repo1.maven.org/maven2/org/python/jython-standalone/2.7.3/jython-standalone-2.7.3.jar

# 或使用 curl
curl -O https://repo1.maven.org/maven2/org/python/jython-standalone/2.7.3/jython-standalone-2.7.3.jar

2. 在 Burp Suite 中設定 Jython

  1. 開啟 Burp Suite
  2. 前往 Extender > Options
  3. 在「Python Environment」區塊中,設定 Jython JAR 檔案路徑
  4. 點擊「Select file」選擇剛下載的 jython-standalone-2.7.3.jar

Burp Suite 擴展 API 架構

核心介面

Burp Suite Extender API 提供了豐富的介面,以下是最重要的幾個:

1
2
3
4
5
6
7
8
IBurpExtender          - 擴展入口點,所有擴展都必須實作此介面
IBurpExtenderCallbacks - 提供擴展與 Burp Suite 互動的回調方法
IHttpListener          - 監聽 HTTP 請求和回應
IProxyListener         - 監聽通過 Proxy 的流量
IScannerCheck          - 實作自訂掃描檢查
IIntruderPayloadGenerator - 自訂 Intruder payload 產生器
IContextMenuFactory    - 建立右鍵選單項目
ITab                   - 建立自訂 UI 標籤頁

API 階層架構

1
2
3
4
5
6
7
8
IBurpExtender (入口點)
    └── IBurpExtenderCallbacks (核心回調)
            ├── IHttpRequestResponse (HTTP 請求/回應)
            ├── IRequestInfo (請求資訊解析)
            ├── IResponseInfo (回應資訊解析)
            ├── IParameter (參數處理)
            ├── IScanIssue (掃描問題)
            └── IExtensionHelpers (輔助工具)

Java 擴展開發

基本擴展結構

每個 Java 擴展都需要實作 IBurpExtender 介面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package burp;

public class BurpExtender implements IBurpExtender {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        // 儲存回調物件
        this.callbacks = callbacks;

        // 取得輔助工具
        this.helpers = callbacks.getHelpers();

        // 設定擴展名稱
        callbacks.setExtensionName("My First Extension");

        // 輸出訊息到 Extender 標籤頁
        callbacks.printOutput("Extension loaded successfully!");
    }
}

專案結構

建議的 Maven 專案結構:

1
2
3
4
5
6
7
8
9
my-burp-extension/
├── pom.xml
├── src/
│   └── main/
│       └── java/
│           └── burp/
│               └── BurpExtender.java
└── target/
    └── my-extension.jar

Maven 設定檔

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-burp-extension</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.portswigger.burp.extender</groupId>
            <artifactId>burp-extender-api</artifactId>
            <version>2.3</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </plugins>
    </build>
</project>

編譯與載入

1
2
3
4
5
# 編譯專案
mvn clean package

# 產生的 JAR 檔案位於 target/ 目錄
ls target/*.jar

載入擴展:

  1. 開啟 Burp Suite
  2. 前往 Extender > Extensions
  3. 點擊「Add」
  4. 選擇「Java」作為擴展類型
  5. 選擇編譯好的 JAR 檔案

Python (Jython) 擴展開發

基本擴展結構

Python 擴展同樣需要實作 IBurpExtender 介面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IHttpListener

class BurpExtender(IBurpExtender, IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        # 儲存回調物件
        self._callbacks = callbacks

        # 取得輔助工具
        self._helpers = callbacks.getHelpers()

        # 設定擴展名稱
        callbacks.setExtensionName("My Python Extension")

        # 註冊 HTTP 監聽器
        callbacks.registerHttpListener(self)

        # 輸出訊息
        print("[*] Extension loaded successfully!")

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        # 處理 HTTP 訊息
        if messageIsRequest:
            request = messageInfo.getRequest()
            analyzedRequest = self._helpers.analyzeRequest(request)
            print("[*] Request URL: " + str(analyzedRequest.getUrl()))

載入 Python 擴展

  1. 確認已設定 Jython 路徑
  2. 前往 Extender > Extensions
  3. 點擊「Add」
  4. 選擇「Python」作為擴展類型
  5. 選擇 .py 檔案

常用介面與回調

IHttpListener - HTTP 監聽器

監聽所有經過 Burp Suite 的 HTTP 流量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class BurpExtender implements IBurpExtender, IHttpListener {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("HTTP Logger");

        // 註冊 HTTP 監聽器
        callbacks.registerHttpListener(this);
    }

    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest,
                                   IHttpRequestResponse messageInfo) {
        // 只處理來自 Proxy 的流量
        if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY) {
            if (messageIsRequest) {
                // 處理請求
                byte[] request = messageInfo.getRequest();
                IRequestInfo requestInfo = helpers.analyzeRequest(request);

                callbacks.printOutput("Request to: " + requestInfo.getUrl());
            } else {
                // 處理回應
                byte[] response = messageInfo.getResponse();
                IResponseInfo responseInfo = helpers.analyzeResponse(response);

                callbacks.printOutput("Response status: " + responseInfo.getStatusCode());
            }
        }
    }
}

IProxyListener - Proxy 監聽器

專門監聽 Proxy 模組的流量,可以攔截和修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BurpExtender implements IBurpExtender, IProxyListener {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("Proxy Modifier");
        callbacks.registerProxyListener(this);
    }

    @Override
    public void processProxyMessage(boolean messageIsRequest,
                                    IInterceptedProxyMessage message) {
        if (messageIsRequest) {
            IHttpRequestResponse messageInfo = message.getMessageInfo();
            byte[] request = messageInfo.getRequest();

            // 解析請求
            IRequestInfo requestInfo = helpers.analyzeRequest(request);

            // 修改請求(例如:新增自訂 Header)
            List<String> headers = new ArrayList<>(requestInfo.getHeaders());
            headers.add("X-Custom-Header: MyValue");

            byte[] body = Arrays.copyOfRange(request,
                requestInfo.getBodyOffset(), request.length);
            byte[] newRequest = helpers.buildHttpMessage(headers, body);

            messageInfo.setRequest(newRequest);
        }
    }
}

IContextMenuFactory - 右鍵選單

新增自訂的右鍵選單項目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class BurpExtender implements IBurpExtender, IContextMenuFactory {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("Context Menu Extension");
        callbacks.registerContextMenuFactory(this);
    }

    @Override
    public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
        List<JMenuItem> menuItems = new ArrayList<>();

        JMenuItem sendToDecoder = new JMenuItem("Decode Base64");
        sendToDecoder.addActionListener(e -> {
            IHttpRequestResponse[] messages = invocation.getSelectedMessages();
            if (messages != null && messages.length > 0) {
                // 處理選取的訊息
                byte[] request = messages[0].getRequest();
                callbacks.printOutput("Processing request...");
            }
        });

        menuItems.add(sendToDecoder);
        return menuItems;
    }
}

HTTP 請求/回應處理

解析 HTTP 請求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void analyzeHttpRequest(IHttpRequestResponse messageInfo) {
    byte[] request = messageInfo.getRequest();
    IRequestInfo requestInfo = helpers.analyzeRequest(request);

    // 取得請求方法
    String method = requestInfo.getMethod();
    callbacks.printOutput("Method: " + method);

    // 取得 URL
    URL url = requestInfo.getUrl();
    callbacks.printOutput("URL: " + url.toString());

    // 取得 Headers
    List<String> headers = requestInfo.getHeaders();
    for (String header : headers) {
        callbacks.printOutput("Header: " + header);
    }

    // 取得參數
    List<IParameter> parameters = requestInfo.getParameters();
    for (IParameter param : parameters) {
        callbacks.printOutput("Param: " + param.getName() +
                             " = " + param.getValue() +
                             " (Type: " + param.getType() + ")");
    }

    // 取得請求 Body
    int bodyOffset = requestInfo.getBodyOffset();
    byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length);
    String bodyStr = new String(body);
    callbacks.printOutput("Body: " + bodyStr);
}

解析 HTTP 回應

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void analyzeHttpResponse(IHttpRequestResponse messageInfo) {
    byte[] response = messageInfo.getResponse();

    if (response == null) {
        callbacks.printOutput("No response available");
        return;
    }

    IResponseInfo responseInfo = helpers.analyzeResponse(response);

    // 取得狀態碼
    short statusCode = responseInfo.getStatusCode();
    callbacks.printOutput("Status Code: " + statusCode);

    // 取得 Headers
    List<String> headers = responseInfo.getHeaders();
    for (String header : headers) {
        callbacks.printOutput("Response Header: " + header);
    }

    // 取得 MIME 類型
    String mimeType = responseInfo.getStatedMimeType();
    callbacks.printOutput("MIME Type: " + mimeType);

    // 取得回應 Body
    int bodyOffset = responseInfo.getBodyOffset();
    byte[] body = Arrays.copyOfRange(response, bodyOffset, response.length);
    String bodyStr = new String(body);
    callbacks.printOutput("Response Body Length: " + body.length);
}

修改 HTTP 請求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public byte[] modifyRequest(byte[] originalRequest) {
    IRequestInfo requestInfo = helpers.analyzeRequest(originalRequest);

    // 取得原始 Headers
    List<String> headers = new ArrayList<>(requestInfo.getHeaders());

    // 修改 User-Agent
    for (int i = 0; i < headers.size(); i++) {
        if (headers.get(i).startsWith("User-Agent:")) {
            headers.set(i, "User-Agent: Custom-Scanner/1.0");
            break;
        }
    }

    // 新增自訂 Header
    headers.add("X-Scanned-By: MyExtension");

    // 取得 Body
    int bodyOffset = requestInfo.getBodyOffset();
    byte[] body = Arrays.copyOfRange(originalRequest, bodyOffset,
                                      originalRequest.length);

    // 建立新的請求
    return helpers.buildHttpMessage(headers, body);
}

參數操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public byte[] modifyParameter(byte[] request, String paramName, String newValue) {
    // 移除舊參數
    IParameter oldParam = helpers.getRequestParameter(request, paramName);
    if (oldParam != null) {
        request = helpers.removeParameter(request, oldParam);
    }

    // 新增新參數
    IParameter newParam = helpers.buildParameter(paramName, newValue,
                                                  IParameter.PARAM_BODY);
    return helpers.addParameter(request, newParam);
}

public byte[] updateUrlParameter(byte[] request, String paramName, String newValue) {
    IParameter oldParam = helpers.getRequestParameter(request, paramName);
    if (oldParam != null) {
        IParameter newParam = helpers.buildParameter(paramName, newValue,
                                                      IParameter.PARAM_URL);
        return helpers.updateParameter(request, newParam);
    }
    return request;
}

Scanner 檢查擴展

實作 IScannerCheck

建立自訂的掃描規則來檢測特定漏洞:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
public class BurpExtender implements IBurpExtender, IScannerCheck {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    // 定義要檢測的敏感資訊模式
    private static final Pattern[] SENSITIVE_PATTERNS = {
        Pattern.compile("api[_-]?key[\"']?\\s*[:=]\\s*[\"']?([a-zA-Z0-9]{20,})",
                        Pattern.CASE_INSENSITIVE),
        Pattern.compile("password[\"']?\\s*[:=]\\s*[\"']?([^\"'\\s]+)",
                        Pattern.CASE_INSENSITIVE),
        Pattern.compile("secret[_-]?key[\"']?\\s*[:=]\\s*[\"']?([a-zA-Z0-9]{16,})",
                        Pattern.CASE_INSENSITIVE)
    };

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("Sensitive Data Scanner");
        callbacks.registerScannerCheck(this);
    }

    @Override
    public List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse) {
        List<IScanIssue> issues = new ArrayList<>();

        byte[] response = baseRequestResponse.getResponse();
        if (response == null) return null;

        IResponseInfo responseInfo = helpers.analyzeResponse(response);
        int bodyOffset = responseInfo.getBodyOffset();
        String responseBody = new String(Arrays.copyOfRange(response,
                                         bodyOffset, response.length));

        // 檢查敏感資訊模式
        for (Pattern pattern : SENSITIVE_PATTERNS) {
            Matcher matcher = pattern.matcher(responseBody);
            if (matcher.find()) {
                issues.add(new CustomScanIssue(
                    baseRequestResponse.getHttpService(),
                    helpers.analyzeRequest(baseRequestResponse).getUrl(),
                    new IHttpRequestResponse[] { baseRequestResponse },
                    "Sensitive Data Exposure",
                    "Potential sensitive data found: " + matcher.group(0),
                    "Medium"
                ));
            }
        }

        return issues.isEmpty() ? null : issues;
    }

    @Override
    public List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse,
                                          IScannerInsertionPoint insertionPoint) {
        // 主動掃描:嘗試注入 payload
        List<IScanIssue> issues = new ArrayList<>();

        // 測試 SQL 注入
        String[] sqlPayloads = {"'", "\"", "' OR '1'='1", "1; DROP TABLE users--"};

        for (String payload : sqlPayloads) {
            byte[] checkRequest = insertionPoint.buildRequest(payload.getBytes());
            IHttpRequestResponse checkResponse = callbacks.makeHttpRequest(
                baseRequestResponse.getHttpService(), checkRequest);

            if (checkResponse.getResponse() != null) {
                String response = new String(checkResponse.getResponse());

                // 檢查錯誤訊息
                if (response.contains("SQL syntax") ||
                    response.contains("mysql_fetch") ||
                    response.contains("ORA-") ||
                    response.contains("PostgreSQL")) {

                    issues.add(new CustomScanIssue(
                        baseRequestResponse.getHttpService(),
                        helpers.analyzeRequest(baseRequestResponse).getUrl(),
                        new IHttpRequestResponse[] { checkResponse },
                        "SQL Injection",
                        "SQL injection vulnerability detected with payload: " + payload,
                        "High"
                    ));
                    break;
                }
            }
        }

        return issues.isEmpty() ? null : issues;
    }

    @Override
    public int consolidateDuplicateIssues(IScanIssue existingIssue,
                                           IScanIssue newIssue) {
        if (existingIssue.getIssueName().equals(newIssue.getIssueName())) {
            return -1; // 保留舊的問題
        }
        return 0; // 兩個問題都保留
    }
}

自訂 Scan Issue 類別

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class CustomScanIssue implements IScanIssue {

    private IHttpService httpService;
    private URL url;
    private IHttpRequestResponse[] httpMessages;
    private String name;
    private String detail;
    private String severity;

    public CustomScanIssue(IHttpService httpService, URL url,
                           IHttpRequestResponse[] httpMessages,
                           String name, String detail, String severity) {
        this.httpService = httpService;
        this.url = url;
        this.httpMessages = httpMessages;
        this.name = name;
        this.detail = detail;
        this.severity = severity;
    }

    @Override
    public URL getUrl() { return url; }

    @Override
    public String getIssueName() { return name; }

    @Override
    public int getIssueType() { return 0; }

    @Override
    public String getSeverity() { return severity; }

    @Override
    public String getConfidence() { return "Certain"; }

    @Override
    public String getIssueBackground() {
        return "This issue was detected by a custom extension.";
    }

    @Override
    public String getRemediationBackground() { return null; }

    @Override
    public String getIssueDetail() { return detail; }

    @Override
    public String getRemediationDetail() { return null; }

    @Override
    public IHttpRequestResponse[] getHttpMessages() { return httpMessages; }

    @Override
    public IHttpService getHttpService() { return httpService; }
}

實用擴展範例

範例 1:請求日誌記錄器

記錄所有 HTTP 請求到檔案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IHttpListener
import datetime
import os

class BurpExtender(IBurpExtender, IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("Request Logger")
        callbacks.registerHttpListener(self)

        # 設定日誌檔案路徑
        self._log_file = "/tmp/burp_requests.log"
        print("[*] Logging requests to: " + self._log_file)

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        if messageIsRequest:
            request = messageInfo.getRequest()
            analyzedRequest = self._helpers.analyzeRequest(messageInfo)

            timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            url = str(analyzedRequest.getUrl())
            method = analyzedRequest.getMethod()

            log_entry = "[{}] {} {}\n".format(timestamp, method, url)

            with open(self._log_file, "a") as f:
                f.write(log_entry)

範例 2:自動新增認證 Header

自動為所有請求新增 Authorization Header:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package burp;

import java.util.*;

public class BurpExtender implements IBurpExtender, IHttpListener {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;
    private String authToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("Auto Auth Header");
        callbacks.registerHttpListener(this);
        callbacks.printOutput("Auto Auth Header extension loaded");
    }

    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest,
                                   IHttpRequestResponse messageInfo) {
        // 只處理請求,且只處理來自 Proxy、Repeater、Intruder 的流量
        if (!messageIsRequest) return;

        if (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY &&
            toolFlag != IBurpExtenderCallbacks.TOOL_REPEATER &&
            toolFlag != IBurpExtenderCallbacks.TOOL_INTRUDER) {
            return;
        }

        byte[] request = messageInfo.getRequest();
        IRequestInfo requestInfo = helpers.analyzeRequest(request);

        // 取得 Headers 並新增認證
        List<String> headers = new ArrayList<>(requestInfo.getHeaders());

        // 移除舊的 Authorization Header(如果存在)
        headers.removeIf(h -> h.toLowerCase().startsWith("authorization:"));

        // 新增新的 Authorization Header
        headers.add("Authorization: " + authToken);

        // 取得 Body
        int bodyOffset = requestInfo.getBodyOffset();
        byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length);

        // 建立新請求
        byte[] newRequest = helpers.buildHttpMessage(headers, body);
        messageInfo.setRequest(newRequest);
    }
}

範例 3:XSS 漏洞掃描器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package burp;

import java.util.*;
import java.net.URL;
import java.util.regex.*;

public class BurpExtender implements IBurpExtender, IScannerCheck {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    private static final String[] XSS_PAYLOADS = {
        "<script>alert('XSS')</script>",
        "<img src=x onerror=alert('XSS')>",
        "'\"><script>alert('XSS')</script>",
        "<svg onload=alert('XSS')>",
        "javascript:alert('XSS')"
    };

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("XSS Scanner");
        callbacks.registerScannerCheck(this);
    }

    @Override
    public List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse) {
        return null; // 只進行主動掃描
    }

    @Override
    public List<IScanIssue> doActiveScan(IHttpRequestResponse baseRequestResponse,
                                          IScannerInsertionPoint insertionPoint) {
        List<IScanIssue> issues = new ArrayList<>();

        for (String payload : XSS_PAYLOADS) {
            // 建立包含 payload 的請求
            byte[] checkRequest = insertionPoint.buildRequest(payload.getBytes());

            // 發送請求
            IHttpRequestResponse checkResponse = callbacks.makeHttpRequest(
                baseRequestResponse.getHttpService(), checkRequest);

            byte[] response = checkResponse.getResponse();
            if (response == null) continue;

            String responseStr = new String(response);

            // 檢查 payload 是否反映在回應中
            if (responseStr.contains(payload)) {
                // 取得 payload 在回應中的位置
                int[] payloadOffsets = getPayloadOffsets(response, payload);

                issues.add(new CustomScanIssue(
                    baseRequestResponse.getHttpService(),
                    helpers.analyzeRequest(baseRequestResponse).getUrl(),
                    new IHttpRequestResponse[] {
                        callbacks.applyMarkers(checkResponse, null,
                            Arrays.asList(payloadOffsets))
                    },
                    "Reflected XSS",
                    "The payload <code>" + escapeHtml(payload) +
                    "</code> was reflected in the response without proper encoding.",
                    "High"
                ));
                break;
            }
        }

        return issues.isEmpty() ? null : issues;
    }

    private int[] getPayloadOffsets(byte[] response, String payload) {
        String responseStr = new String(response);
        int start = responseStr.indexOf(payload);
        if (start == -1) return new int[] {0, 0};
        return new int[] {start, start + payload.length()};
    }

    private String escapeHtml(String input) {
        return input.replace("&", "&amp;")
                   .replace("<", "&lt;")
                   .replace(">", "&gt;")
                   .replace("\"", "&quot;");
    }

    @Override
    public int consolidateDuplicateIssues(IScanIssue existingIssue,
                                           IScanIssue newIssue) {
        if (existingIssue.getUrl().equals(newIssue.getUrl()) &&
            existingIssue.getIssueName().equals(newIssue.getIssueName())) {
            return -1;
        }
        return 0;
    }
}

範例 4:自訂 Intruder Payload 產生器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package burp;

import java.util.*;

public class BurpExtender implements IBurpExtender,
                                      IIntruderPayloadGeneratorFactory {

    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        this.callbacks = callbacks;
        this.helpers = callbacks.getHelpers();
        callbacks.setExtensionName("Custom Payload Generator");
        callbacks.registerIntruderPayloadGeneratorFactory(this);
    }

    @Override
    public String getGeneratorName() {
        return "Sequential Number Generator";
    }

    @Override
    public IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack) {
        return new SequentialPayloadGenerator();
    }

    class SequentialPayloadGenerator implements IIntruderPayloadGenerator {
        private int currentValue = 1;
        private int maxValue = 1000;

        @Override
        public boolean hasMorePayloads() {
            return currentValue <= maxValue;
        }

        @Override
        public byte[] getNextPayload(byte[] baseValue) {
            String payload = String.valueOf(currentValue);
            currentValue++;
            return payload.getBytes();
        }

        @Override
        public void reset() {
            currentValue = 1;
        }
    }
}

除錯與最佳實踐

除錯技巧

  1. 使用 Output 和 Alerts
1
2
3
4
5
6
7
8
// 輸出到 Extender 標籤頁的 Output
callbacks.printOutput("Debug message: " + variable);

// 輸出錯誤訊息
callbacks.printError("Error occurred: " + e.getMessage());

// 顯示警告對話框
callbacks.issueAlert("Something important happened!");
  1. 異常處理
1
2
3
4
5
6
7
8
9
try {
    // 可能出錯的程式碼
    byte[] response = messageInfo.getResponse();
    IResponseInfo responseInfo = helpers.analyzeResponse(response);
} catch (Exception e) {
    callbacks.printError("Exception: " + e.getClass().getName() +
                         " - " + e.getMessage());
    e.printStackTrace(new PrintStream(callbacks.getStderr()));
}
  1. 使用 Java 除錯器
1
2
3
# 啟動 Burp Suite 並開啟遠端除錯
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 \
     -jar burpsuite_pro.jar

最佳實踐

  1. 效能考量
1
2
3
4
5
6
7
8
9
// 避免在 processHttpMessage 中進行耗時操作
@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest,
                               IHttpRequestResponse messageInfo) {
    // 使用執行緒處理耗時任務
    new Thread(() -> {
        processMessageAsync(messageInfo);
    }).start();
}
  1. 資源清理
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class BurpExtender implements IBurpExtender, IExtensionStateListener {

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        callbacks.registerExtensionStateListener(this);
    }

    @Override
    public void extensionUnloaded() {
        // 清理資源
        // 關閉資料庫連線、檔案控制代碼等
        callbacks.printOutput("Extension unloaded, cleaning up resources...");
    }
}
  1. 執行緒安全
1
2
3
4
5
// 使用同步集合
private List<String> logEntries = Collections.synchronizedList(new ArrayList<>());

// 或使用 ConcurrentHashMap
private Map<String, Integer> requestCounts = new ConcurrentHashMap<>();

總結

Burp Suite 擴展開發是提升滲透測試效率的有效方式。本文涵蓋了以下主題:

  1. API 概述:了解核心介面和回調機制
  2. 開發環境設定:Java 和 Python (Jython) 環境配置
  3. HTTP 處理:請求和回應的解析與修改
  4. Scanner 擴展:建立自訂的漏洞掃描規則
  5. 實用範例:日誌記錄、自動認證、XSS 掃描器等

進階學習資源

掌握 Burp Suite 擴展開發,能讓您根據特定需求客製化工具,大幅提升滲透測試的效率與深度。建議從簡單的擴展開始,逐步學習更複雜的功能,並參考 BApp Store 中優秀擴展的原始碼來學習實作技巧。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy