Burp Extension開発入門:Pythonで作る初めての拡張機能 🚀

Webセキュリティ

Burp SuiteをPython (Jython) で拡張して、セキュリティテストを自動化・効率化しよう!

Webアプリケーションのセキュリティ診断に欠かせないツール、Burp Suite。その強力な機能をさらに拡張し、独自のニーズに合わせてカスタマイズできるのがBurp Extensionです。

Burp Extensionを使えば、以下のようなことが可能になります。

  • 繰り返し行う作業の自動化
  • 特定の脆弱性パターンに特化したスキャンロジックの追加
  • * 独自のUIコンポーネントの追加による分析支援 * 他のツールとの連携強化

Burp ExtensionはJava、Python (Jythonを使用)、Ruby (JRubyを使用) で開発できますが、この記事ではPython (Jython) を使った開発に焦点を当て、基本的な拡張機能の作り方をステップバイステップで解説します。Pythonの書きやすさと豊富なライブラリ(一部Jython環境での制約あり)を活用して、あなただけの強力なBurp Suiteを作り上げましょう! 💪

まず、Burp ExtensionをPythonで開発するために必要な環境を整えましょう。

1. Burp Suiteのインストール

当然ですが、Burp Suite本体が必要です。まだインストールしていない場合は、PortSwiggerの公式サイトからダウンロードしてインストールしてください。Community Edition (無料) でも基本的な拡張機能の開発と利用は可能です。

Burp Suite Community Edition ダウンロード

この記事では、Burp Suiteが既にインストールされ、基本的な操作に慣れていることを前提とします。

2. Jython Standalone版のダウンロード

Burp SuiteはJavaで書かれているため、Pythonコードを実行するにはJythonが必要です。JythonはPythonコードをJavaバイトコードにコンパイルしてJVM上で実行するための実装です。

Burp Extension開発にはJython Standalone版 (jython-standalone-*.jar) を使用します。公式サイトから最新版をダウンロードしてください。

Jython ダウンロードページ

ダウンロードした `jython-standalone-*.jar` ファイルは、任意の場所に保存しておきましょう。後でBurp Suiteに設定します。

注意点: JythonはCPython (通常のPython) と100%互換ではありません。特にC言語で書かれた拡張モジュール (NumPy, Pandasなど) は基本的に利用できません。標準ライブラリの多くは利用可能ですが、一部動作しないものもあります。

3. Burp SuiteでのJythonの設定

ダウンロードしたJythonをBurp Suiteに認識させる設定を行います。

  1. Burp Suiteを起動します。
  2. [Extender] タブに移動します。
  3. その中の [Options] サブタブを開きます。
  4. Python Environment のセクションを見つけます。
  5. [Location of Jython standalone JAR file] の横にある [Select file …] ボタンをクリックします。
  6. 先ほどダウンロードした `jython-standalone-*.jar` ファイルを選択します。

これで、Burp SuiteがPython拡張機能を実行する準備が整いました。 🎉

Pythonライブラリの追加: もし追加のPythonライブラリ (Jython互換のもの) を使いたい場合は、[Folder for loading modules] でライブラリが格納されたフォルダを指定できます。pipでインストールしたライブラリをJython環境で使う場合は、そのライブラリのパスを指定します。

Burp Extensionは、PortSwiggerが提供するBurp Extender APIを利用して作成します。このAPIはJavaインターフェースの集まりですが、Jythonを通じてPythonからこれらのインターフェースを実装・利用できます。

`IBurpExtender` インターフェース

すべてのBurp Extensionは、`burp.IBurpExtender` インターフェースを実装する必要があります。これは拡張機能のエントリーポイントとなる最も基本的なインターフェースです。

このインターフェースには、`registerExtenderCallbacks` というメソッドが1つだけ定義されています。


from burp import IBurpExtender

class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, callbacks):
        # ここに拡張機能の初期化処理を書く
        pass
      

`registerExtenderCallbacks` メソッド

このメソッドは、Burp Suiteが拡張機能をロードする際に一度だけ呼び出されます。引数として `burp.IExtenderCallbacks` オブジェクトを受け取ります。

このメソッド内で行うべき主な処理は以下の通りです。

  • 拡張機能の名前やバージョンを設定する。
  • * 必要なコールバックやリスナーを登録する。 * ヘルパーオブジェクトを取得して保持する。
  • カスタムUIコンポーネントを初期化・登録する。

`IExtenderCallbacks` オブジェクト

`registerExtenderCallbacks` メソッドで受け取る `callbacks` オブジェクトは、Burp Suiteの様々な機能と連携するためのメソッドを提供します。非常に重要なオブジェクトです。

主な機能には以下のようなものがあります。

メソッド例説明
setExtensionName("拡張機能名")Extenderタブに表示される拡張機能の名前を設定します。
getHelpers()リクエスト/レスポンスの解析や操作に便利なメソッドを持つ IExtenderHelpers オブジェクトを取得します。
registerHttpListener(listener)HTTPリクエスト/レスポンスを処理するリスナー (IHttpListener を実装したクラスのインスタンス) を登録します。
registerMessageEditorTabFactory(factory)HTTPメッセージエディタにカスタムタブを追加するファクトリ (IMessageEditorTabFactory を実装したクラスのインスタンス) を登録します。
registerScannerCheck(check)カスタムスキャンチェック (IScannerCheck を実装したクラスのインスタンス) を登録します。
printOutput("メッセージ")拡張機能のOutputタブにメッセージを出力します。デバッグに便利です。
printError("エラーメッセージ")拡張機能のErrorタブにエラーメッセージを出力します。
sendToRepeater(...), sendToIntruder(...), etc.特定のHTTPメッセージを他のBurpツールに送信します。
makeHttpRequest(...)拡張機能から能動的にHTTPリクエストを送信します。

これらのメソッドを駆使して、Burp Suiteの動作を拡張していきます。

主要なインターフェース

`IBurpExtender` 以外にも、特定の機能を実現するために実装する主要なインターフェースがあります。

  • `IHttpListener`: Proxy経由のHTTPリクエスト/レスポンスをリアルタイムで傍受し、参照・変更できます。
  • `IMessageEditorTabFactory` / `IMessageEditorTab`: HTTPリクエスト/レスポンスビューアにカスタムタブを追加し、独自の視点での情報表示や操作を提供します。
  • `IScannerCheck`: Burp Scannerにカスタムの脆弱性スキャンロジック(アクティブ/パッシブ)を追加します。
  • `IContextMenuFactory` / `IContextMenuInvocation`: Burp Suite内の様々なコンテキストメニューにカスタム項目を追加します。
  • `ITab`: Burp SuiteのメインUIに独自のタブを追加します。

これらを組み合わせることで、多機能な拡張機能を作成できます。

理論ばかりでは退屈なので、実際に簡単な拡張機能を作ってみましょう。この拡張機能は、ロードされたときにOutputタブにメッセージを表示するだけのシンプルなものです。

以下のPythonコードを `hello_burp.py` のような名前で保存してください。


# -*- coding: utf-8 -*-
# Jythonで日本語を扱うためのおまじない

from burp import IBurpExtender
from burp import IExtensionStateListener # 拡張機能の状態変化を受け取るため

# Javaの標準ライブラリを使うことも可能
from javax.swing import JOptionPane

class BurpExtender(IBurpExtender, IExtensionStateListener):

    # IBurpExtenderの必須メソッド
    def registerExtenderCallbacks(self, callbacks):
        # callbacksオブジェクトをクラス変数として保持
        self._callbacks = callbacks
        # ヘルパーオブジェクトを取得して保持
        self._helpers = callbacks.getHelpers()

        # 拡張機能の名前を設定
        extension_name = "Hello Burp Python"
        callbacks.setExtensionName(extension_name)

        # 拡張機能の状態変化リスナーを登録 (自分自身を登録)
        callbacks.registerExtensionStateListener(self)

        # Outputタブにメッセージを出力
        message = u"こんにちは、Burp Extensionの世界へ! {} がロードされました。🐍".format(extension_name)
        callbacks.printOutput(message)

        # Python/Jythonのバージョン情報を出力
        try:
            import sys
            py_version = sys.version
            callbacks.printOutput(u"Python (Jython) バージョン: {}".format(py_version))
        except Exception as e:
            callbacks.printError(u"バージョン情報の取得に失敗: {}".format(e))

        # JavaのUIを使ってみる (おまけ)
        # JOptionPane.showMessageDialog(None, message) # BurpのUIスレッド外からSwingを呼ぶのは非推奨

        print("registerExtenderCallbacks finished.") # コンソールにも出力 (デバッグ用)
        return

    # IExtensionStateListenerのメソッド
    def extensionUnloaded(self):
        # 拡張機能がアンロードされるときに呼び出される
        print("Extension unloaded.")
        self._callbacks.printOutput("Hello Burp Python 拡張機能がアンロードされました。さようなら! 👋")
        return

      

コードの解説

  • `from burp import …`: Burp Extender APIのクラスやインターフェースをインポートします。
  • `class BurpExtender(IBurpExtender, IExtensionStateListener)`: `IBurpExtender` を継承したクラスを定義します。ここではアンロード時の処理も加えるため `IExtensionStateListener` も継承しています。
  • `registerExtenderCallbacks` メソッド内:
    • `callbacks` と `helpers` オブジェクトを後で使えるように `self._callbacks`, `self._helpers` に保存しています。
    • `setExtensionName` で拡張機能の名前を設定します。
    • `registerExtensionStateListener(self)` で、アンロード時に `extensionUnloaded` メソッドが呼ばれるようにします。
    • `printOutput` でOutputタブにメッセージを表示します。日本語を扱うため、文字列の前に `u` をつけてユニコード文字列としています (Python 2系 Jythonの場合)。
    • おまけでPythonのバージョン情報を取得して表示しています。
    • `print` 文はBurp Suiteを起動したコンソールの標準出力/エラー出力に表示されます (デバッグ時に便利)。
  • `extensionUnloaded` メソッド内:
    • 拡張機能がアンロードされる際に、コンソールとOutputタブにメッセージを出力します。

Burp Suiteへの読み込み手順

  1. Burp Suiteを起動します。
  2. [Extender] タブ -> [Extensions] サブタブを開きます。
  3. [Add] ボタンをクリックします。
  4. Extension details ダイアログが表示されます。
    • Extension type:`Python` を選択します。
    • Extension file (.py):[Select file …] ボタンをクリックし、先ほど保存した `hello_burp.py` ファイルを選択します。
  5. [Next] ボタンをクリックします。
  6. エラーがなければ、Outputタブに “こんにちは、Burp Extensionの世界へ!…” というメッセージが表示されるはずです。Extensionsタブのリストにも “Hello Burp Python” が追加され、チェックボックスがオンになります。
  7. もしErrorタブに何か表示された場合は、コードやJythonの設定を見直してください。

これで、最初のBurp Extensionが動作しました! 🎉 簡単でしたね。

拡張機能をアンロードするには、Extensionsタブで該当の拡張機能を選択し、チェックボックスをオフにするか、[Remove] ボタンをクリックします。アンロード時に `extensionUnloaded` メソッドが呼ばれ、指定したメッセージがOutputタブに表示されるはずです。

Burp Extensionで最もよく使われる機能の一つが、Proxyを通過するHTTPメッセージの傍受と変更です。これを実現するのが `burp.IHttpListener` インターフェースです。

`IHttpListener` インターフェース

`IHttpListener` インターフェースには `processHttpMessage` というメソッドが一つだけ定義されています。


from burp import IHttpListener

class MyHttpListener(IHttpListener):
    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        # ここにHTTPメッセージの処理を書く
        pass
      

このメソッドは、Burp Suiteの各ツール (Proxy, Repeater, Scannerなど) をHTTPメッセージが通過するたびに呼び出されます。

引数の意味は以下の通りです。

  • `toolFlag`: メッセージを処理しているBurpツールを示すフラグ (整数値)。`callbacks.TOOL_PROXY`, `callbacks.TOOL_REPEATER` などと比較して、どのツールからの呼び出しかを判別できます。定数値は `IExtenderCallbacks` インターフェースで定義されています。
  • `messageIsRequest`: メッセージがリクエストの場合は `True`、レスポンスの場合は `False` となります。
  • `messageInfo`: 処理対象のHTTPメッセージに関する情報を持つオブジェクト (型は `IHttpRequestResponse`)。リクエスト/レスポンスのバイト列、HTTPサービス情報 (ホスト、ポート、プロトコル) などを取得・設定できます。

実装例: 特定のヘッダーを追加する

ここでは例として、Proxyを通過するすべてのリクエストに `X-Burp-Extension: MyPythonExtension` というカスタムヘッダーを追加する拡張機能を作成してみましょう。

以下のコードを `add_header_extension.py` として保存します。


# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IHttpListener
from java.io import PrintWriter # Output/Errorタブへの出力に使用

class BurpExtender(IBurpExtender, IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()

        callbacks.setExtensionName("Add Custom Header")

        # 標準出力と標準エラー出力を取得 (デバッグ用)
        self._stdout = PrintWriter(callbacks.getStdout(), True)
        self._stderr = PrintWriter(callbacks.getStderr(), True)

        # HTTPリスナーとして自身を登録
        callbacks.registerHttpListener(self)

        self._stdout.println("Add Custom Header extension loaded.")
        callbacks.printOutput("Add Custom Header 拡張機能がロードされました。")
        return

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        # Proxyからのリクエストのみを対象とする
        if toolFlag == self._callbacks.TOOL_PROXY and messageIsRequest:
            # self._stdout.println("Processing request from Proxy...") # デバッグ出力

            # リクエスト情報を取得
            requestBytes = messageInfo.getRequest()
            requestInfo = self._helpers.analyzeRequest(messageInfo) # getRequest() より便利

            # ヘッダーリストを取得 (変更可能なリストが返る)
            headers = requestInfo.getHeaders()

            # カスタムヘッダーを追加
            custom_header = "X-Burp-Extension: MyPythonExtension"
            headers.add(custom_header)
            # self._stdout.println("Added header: {}".format(custom_header))

            # 変更されたヘッダーと元のボディで新しいリクエストを構築
            # bodyBytes = requestBytes[requestInfo.getBodyOffset():] # ボディ部分を取得
            bodyBytes = requestBytes[requestInfo.getBodyOffset():].tostring() # Javaバイト配列をPython文字列に変換
            
            newRequestBytes = self._helpers.buildHttpMessage(headers, bodyBytes)

            # 変更したリクエストをmessageInfoに設定
            messageInfo.setRequest(newRequestBytes)
            # self._stdout.println("Request updated.")

        # レスポンスの処理 (今回は何もしない)
        # elif toolFlag == self._callbacks.TOOL_PROXY and not messageIsRequest:
            # responseBytes = messageInfo.getResponse()
            # responseInfo = self._helpers.analyzeResponse(responseBytes)
            # ...
        return
      

コードの解説と注意点

  • `IHttpListener` も実装するため、クラス定義で継承します。
  • `registerExtenderCallbacks` 内で `callbacks.registerHttpListener(self)` を呼び出し、このクラスの `processHttpMessage` が呼ばれるように登録します。
  • `processHttpMessage` 内:
    • `toolFlag == self._callbacks.TOOL_PROXY` と `messageIsRequest` で、Proxy経由のリクエストのみを処理対象としています。他のツール (Repeaterなど) やレスポンスは無視します。
    • `messageInfo.getRequest()` でリクエスト全体のバイト配列を取得できます。
    • より便利なのが `self._helpers.analyzeRequest(messageInfo)` です。これはリクエストを解析し、ヘッダーリスト (`java.util.List`)、HTTPメソッド、URL、ボディ開始位置などを簡単に取得できる `IRequestInfo` オブジェクトを返します。
    • `requestInfo.getHeaders()` でヘッダーリストを取得し、`.add()` メソッドで新しいヘッダーを追加します。注意: `getHeaders()` が返すリストは直接変更可能です。
    • リクエストボディを取得します。`requestBytes[requestInfo.getBodyOffset():]` でボディ部分のバイト配列スライスを取得できます。.tostring() を使ってJythonのバイト配列をPythonの文字列(バイト列)に変換しています。Python 3系のJythonでは挙動が異なる可能性があるので注意が必要です。
    • `self._helpers.buildHttpMessage(headers, bodyBytes)` を使って、変更後のヘッダーリストと元のボディから新しいHTTPリクエスト全体のバイト配列を構築します。
    • 最後に `messageInfo.setRequest(newRequestBytes)` を呼び出して、Burp Suiteが実際に送信するリクエストデータを書き換えます。これを忘れると変更が反映されません❗
  • Javaの `PrintWriter` を使ってOutput/Errorタブに出力する方法も示しています。`callbacks.printOutput/printError` とほぼ同じですが、よりJavaライクな書き方です。
  • パフォーマンス: `processHttpMessage` は非常に頻繁に呼び出される可能性があるため、ここでの処理は可能な限り効率的に行う必要があります。重い処理を行うとBurp Suite全体の動作が遅くなる可能性があります。
  • バイト配列と文字列: Burp APIは主にバイト配列 (`byte[]`) を扱います。Jython (Python 2系) では `str` がバイト列に近いですが、厳密には異なる場合があります。`helpers` の `bytesToString` や `stringToBytes` を適切に使う、あるいは `.tostring()` や `array.array(‘b’, python_string)` のような変換が必要になることがあります。特にマルチバイト文字を扱う際はエンコーディングに注意が必要です。

この拡張機能をロードしてBurp Proxy経由でWebサイトにアクセスすると、開発者ツールなどでリクエストヘッダーを確認すると `X-Burp-Extension: MyPythonExtension` が追加されていることが確認できるはずです。✅

同様の手順で、レスポンスを処理 (`messageIsRequest` が `False` の場合) してレスポンスヘッダーを変更したり、`analyzeResponse` や `buildHttpMessage` を使ってレスポンスボディを書き換えることも可能です。

Burp Suiteのリクエスト/レスポンスビューアに独自のタブを追加し、メッセージに関する特定の情報を表示したり、独自の解析結果を表示したりできます。これを実現するのが `IMessageEditorTabFactory` と `IMessageEditorTab` インターフェースです。

インターフェースの役割

  • `IMessageEditorTabFactory`: Burp Suiteに対して、「こういう条件のメッセージが表示されるときに、私のカスタムタブを作ってね」と指示する役割を持ちます。`createNewInstance` メソッドを実装します。
  • `IMessageEditorTab`: 実際にタブに表示される内容や動作を定義します。タブのタイトル (`getTabCaption`)、表示するUIコンポーネント (`getUiComponent`)、タブを表示すべきかどうかの判定 (`isEnabled`)、表示内容の更新 (`setMessage`) などのメソッドを実装します。

実装例: リクエストヘッダー数を表示するタブ

ここでは例として、HTTPリクエストが表示されたときに、そのリクエストに含まれるヘッダーの数を表示するシンプルなカスタムタブを追加する拡張機能を作成してみましょう。

以下のコードを `header_count_tab.py` として保存します。


# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IMessageEditorTabFactory
from burp import IMessageEditorTab
from javax.swing import JPanel, JLabel, JScrollPane, JTextArea # Java Swing UIコンポーネント
from java.awt import BorderLayout

class BurpExtender(IBurpExtender, IMessageEditorTabFactory):

    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("Header Count Tab")

        # メッセージエディタタブファクトリとして自身を登録
        callbacks.registerMessageEditorTabFactory(self)

        callbacks.printOutput("Header Count Tab extension loaded.")
        return

    # IMessageEditorTabFactory のメソッド
    def createNewInstance(self, controller, editable):
        # 新しいタブのインスタンスを作成して返す
        # controller: 現在のメッセージエディタに関する情報を持つ (今回は未使用)
        # editable: エディタが編集可能かどうか (今回は未使用)
        return HeaderCountTab(self._callbacks, self._helpers, editable)

# カスタムタブの実装
class HeaderCountTab(IMessageEditorTab):
    def __init__(self, callbacks, helpers, editable):
        self._callbacks = callbacks
        self._helpers = helpers
        self._editable = editable # 編集可能かどうかのフラグ

        # タブに表示するUIコンポーネントを作成
        self._panel = JPanel(BorderLayout()) # Swingのパネル
        self._header_label = JLabel("Header count will appear here.") # ラベル
        self._text_area = JTextArea() # ヘッダー一覧表示用 (おまけ)
        self._text_area.setEditable(False)
        scroll_pane = JScrollPane(self._text_area)

        self._panel.add(self._header_label, BorderLayout.NORTH)
        self._panel.add(scroll_pane, BorderLayout.CENTER)

        # 現在表示中のメッセージデータを保持する変数
        self._current_message = None
        return

    # IMessageEditorTab のメソッド: タブのタイトルを返す
    def getTabCaption(self):
        return "Header Count"

    # IMessageEditorTab のメソッド: タブのUIコンポーネントを返す
    def getUiComponent(self):
        return self._panel

    # IMessageEditorTab のメソッド: タブを有効にするかどうかを判定する
    def isEnabled(self, content, isRequest):
        # リクエストの場合のみタブを有効にする
        # content: 現在のメッセージのバイト配列
        return isRequest

    # IMessageEditorTab のメソッド: 表示するメッセージが変更されたときに呼ばれる
    def setMessage(self, content, isRequest):
        if isRequest and content:
            # リクエストの場合のみ処理
            requestInfo = self._helpers.analyzeRequest(content)
            headers = requestInfo.getHeaders()
            header_count = len(headers)
            self._header_label.setText("Number of headers: {}".format(header_count))

            # ヘッダー一覧をテキストエリアに表示 (おまけ)
            header_text = "\n".join(headers)
            self._text_area.setText(header_text)
            self._text_area.setCaretPosition(0) # スクロールを先頭に

            # 現在のメッセージを保存
            self._current_message = content
        else:
            # リクエスト以外、またはメッセージがない場合はクリア
            self._header_label.setText("N/A (Not a request or no content)")
            self._text_area.setText("")
            self._current_message = None
        return

    # IMessageEditorTab のメソッド: 現在表示しているメッセージデータを返す (編集可能な場合に重要)
    def getMessage(self):
        # 今回は表示のみなので、元のメッセージをそのまま返す
        # もしUI上でメッセージを編集できるようにした場合、ここで編集後のバイト配列を返す必要がある
        return self._current_message

    # IMessageEditorTab のメソッド: メッセージが編集されたかどうかを返す
    def isModified(self):
        # 今回は編集しないのでFalse
        return False

    # IMessageEditorTab のメソッド: 現在選択されているデータを返す (編集可能な場合に重要)
    def getSelectedData(self):
        # 今回は選択機能がないのでNone
        # もしUI上でテキスト選択などを可能にした場合、ここで選択されたバイト配列を返す
        return None

        

コードの解説とポイント

  • `BurpExtender` クラスは `IMessageEditorTabFactory` を実装し、`registerExtenderCallbacks` 内で `callbacks.registerMessageEditorTabFactory(self)` を呼び出してファクトリを登録します。
  • `createNewInstance` メソッドは、Burp Suiteが新しいタブを必要とするたびに呼び出され、`HeaderCountTab` クラスの新しいインスタンスを返します。
  • `HeaderCountTab` クラスが `IMessageEditorTab` インターフェースを実装し、実際のタブの動作を定義します。
    • `__init__`: タブに表示するUIコンポーネント (ここではJava Swingの `JPanel`, `JLabel`, `JTextArea`) を作成し、初期化します。JythonからJavaのクラスを直接利用できるのがポイントです。
    • `getTabCaption`: タブに表示される名前 (“Header Count”) を返します。
    • `getUiComponent`: `__init__` で作成したUIコンポーネント (`JPanel`) を返します。Burp Suiteがこれをタブ内に表示します。
    • `isEnabled`: このタブが表示されるべき条件を定義します。ここでは `isRequest` が `True` (つまりリクエストメッセージ) の場合にのみ `True` を返し、タブが表示されるようにします。レスポンスのときはタブは表示されません。
    • `setMessage`: メッセージビューアで表示されるメッセージが変わるたびに呼び出されます。`content` (メッセージのバイト配列) と `isRequest` を受け取ります。
      • リクエストの場合、`helpers.analyzeRequest` でヘッダーを取得し、その数を数えて `JLabel` (`_header_label`) のテキストを更新します。
      • おまけで、ヘッダーの一覧を `JTextArea` (`_text_area`) に表示しています。
      • 現在のメッセージ内容 (`content`) を `_current_message` に保存しておきます。これは `getMessage` で必要になります。
      • リクエスト以外の場合は、ラベルやテキストエリアをクリアします。
    • `getMessage`: 現在タブが表示しているメッセージのバイト配列を返します。今回はメッセージの編集機能はないため、`setMessage` で受け取った元のメッセージ (`_current_message`) をそのまま返します。もし編集可能なタブを作る場合は、UIでの変更を反映したバイト配列をここで返す必要があります。
    • `isModified`: メッセージが編集されたかどうかを示すフラグを返します。今回は編集しないので常に `False` です。
    • `getSelectedData`: タブ内で現在選択されているデータのバイト配列を返します。今回は選択機能がないので `None` です。
  • UIライブラリ: この例ではJava Swingを使用しました。Jythonから直接使えるため、最も手軽な方法です。他のPython GUIライブラリ (Tkinterなど) は、Jython環境での動作保証がない、または追加の設定が必要な場合があります。
  • スレッド: Swingコンポーネントの更新は、通常イベントディスパッチスレッド (EDT) で行うべきですが、Burp Extensionの `setMessage` などは必ずしもEDTから呼ばれるとは限りません。簡単なラベル更新程度なら問題ないことが多いですが、複雑なUI操作や時間のかかる処理を行う場合は `SwingUtilities.invokeLater` などを使ってEDTで実行することを検討する必要があります。

この拡張機能をロードし、Proxy履歴などでHTTPリクエストを選択すると、リクエストビューアのタブ群の中に “Header Count” という新しいタブが表示され、ヘッダー数が表示されるはずです。レスポンスを選択したときは、このタブは表示されません。✅

Burp Scannerの機能を拡張し、独自の脆弱性検知ロジックを追加することができます。これが `burp.IScannerCheck` インターフェースの役割です。

独自のチェックを追加することで、特定のフレームワークに固有の脆弱性、社内ルールに基づくセキュリティポリシー違反、最新の脆弱性などを自動で検出できるようになります。

スキャンの種類とメソッド

`IScannerCheck` インターフェースは、主に2種類のスキャンに対応するメソッドを提供します。

  • パッシブスキャン (`doPassiveScan`): Burp Suiteを通過するリクエストとレスポンスを受動的に分析し、脆弱性の兆候を探します。新たなリクエストを送信することはありません。パフォーマンスへの影響が比較的小さいです。
    • 例: レスポンスヘッダーに `Server` ヘッダーが含まれているか、特定のJavaScriptライブラリの古いバージョンが使われていないか、HTMLコメントに機密情報が含まれていないか、など。
  • アクティブスキャン (`doActiveScan`): 対象のアプリケーションに対して能動的に改変したリクエストを送信し、そのレスポンスを分析して脆弱性を検出します。より多くの脆弱性を発見できる可能性がありますが、アプリケーションに負荷をかけたり、状態を変化させたりする可能性があります。
    • 例: SQLインジェクション、クロスサイトスクリプティング (XSS)、OSコマンドインジェクションなどのペイロードを挿入したリクエストを送信し、レスポンスの変化を観察する。

また、`consolidateDuplicateIssues` というメソッドもあり、同じ脆弱性が複数回報告されるのを抑制(統合)するために使われます。

実装例: Serverヘッダー検出 (パッシブスキャン)

非常に簡単な例として、レスポンスに `Server` ヘッダーが含まれている場合に、情報提供 (Information) レベルのレポートを出すパッシブスキャンチェックを作成してみましょう。

以下のコードを `server_header_check.py` として保存します。


# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IScannerCheck
from burp import IScanIssue
from java.io import PrintWriter
from java.util import ArrayList, List # Javaのリスト

class BurpExtender(IBurpExtender, IScannerCheck):

    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("Server Header Check (Passive)")

        self._stdout = PrintWriter(callbacks.getStdout(), True)
        self._stderr = PrintWriter(callbacks.getStderr(), True)

        # スキャナーチェックとして自身を登録
        callbacks.registerScannerCheck(self)

        self._stdout.println("Server Header Check extension loaded.")
        callbacks.printOutput("Server Header Check 拡張機能がロードされました。")
        return

    # IScannerCheck のメソッド: パッシブスキャンを実行
    def doPassiveScan(self, baseRequestResponse):
        # baseRequestResponse: スキャン対象のリクエスト/レスポンス (IHttpRequestResponse)
        self._stdout.println("Passive scan check running...")

        # レスポンスを解析
        responseBytes = baseRequestResponse.getResponse()
        if not responseBytes:
            return None # レスポンスがない場合は何もしない

        responseInfo = self._helpers.analyzeResponse(responseBytes)
        headers = responseInfo.getHeaders()

        # Serverヘッダーを探す
        server_header_value = None
        for header in headers:
            if header.lower().startswith("server:"):
                server_header_value = header
                break

        if server_header_value:
            self._stdout.println("Found Server header: {}".format(server_header_value))
            # Serverヘッダーが見つかったらIssueを作成
            issues = ArrayList() # JavaのArrayListを作成
            issues.add(CustomScanIssue(
                baseRequestResponse.getHttpService(),
                self._helpers.analyzeRequest(baseRequestResponse).getUrl(),
                [self._callbacks.applyMarkers(baseRequestResponse, None, None)], # Issue箇所を示すマーカー (今回はリクエスト全体)
                "Server Header Found",
                "The application response contains a 'Server' header: {}

Revealing the specific server software and version can provide attackers with valuable information for exploiting known vulnerabilities.".format(self._helpers.htmlEncode(server_header_value)), # 詳細説明 (HTML可) "Information", # Severity: High, Medium, Low, Information, False positive "Certain" # Confidence: Certain, Firm, Tentative )) return issues # 作成したIssueのリスト (List) を返す # Serverヘッダーが見つからなければ何も返さない (None) return None # IScannerCheck のメソッド: アクティブスキャンを実行 (今回は実装しない) def doActiveScan(self, baseRequestResponse, insertionPoint): # insertionPoint: ペイロード挿入箇所に関する情報 (IScannerInsertionPoint) return None # 何もしない # IScannerCheck のメソッド: 重複Issueを統合 (今回は簡単な実装) def consolidateDuplicateIssues(self, existingIssue, newIssue): # URLとIssue名が同じなら重複とみなす if existingIssue.getUrl() == newIssue.getUrl() and existingIssue.getIssueName() == newIssue.getIssueName(): return -1 # 重複として新しいIssueを報告しない else: return 0 # 重複ではない # カスタムスキャンIssueの実装 class CustomScanIssue(IScanIssue): def __init__(self, httpService, url, httpMessages, name, detail, severity, confidence): self._httpService = httpService self._url = url self._httpMessages = httpMessages # Issueに関連するリクエスト/レスポンスのリスト self._name = name self._detail = detail self._severity = severity self._confidence = confidence def getUrl(self): return self._url def getIssueName(self): return self._name def getIssueType(self): # Issueタイプを一意に識別する番号 (任意) # 異なる種類のIssueには異なる番号を割り当てる return 0x08000001 # 例: 拡張機能用のカスタムタイプ範囲 def getSeverity(self): return self._severity def getConfidence(self): return self._confidence def getIssueBackground(self): # Issueの背景情報 (一般的な説明) return None def getRemediationBackground(self): # 修正方法の背景情報 return None def getIssueDetail(self): # Issueの詳細 (このインスタンス固有の情報) return self._detail def getRemediationDetail(self): # このインスタンス固有の修正情報 return None def getHttpMessages(self): # Issueに関連するリクエスト/レスポンス (マーカー付き) return self._httpMessages def getHttpService(self): # Issueが発生したHTTPサービス (ホスト、ポートなど) return self._httpService

コードの解説とポイント

  • `BurpExtender` クラスは `IScannerCheck` を実装し、`registerExtenderCallbacks` 内で `callbacks.registerScannerCheck(self)` を呼び出してスキャナーチェックを登録します。
  • `doPassiveScan` メソッドがパッシブスキャンのロジックを実装します。
    • 引数 `baseRequestResponse` はスキャン対象のリクエスト/レスポンスペアです。
    • `baseRequestResponse.getResponse()` でレスポンスのバイト配列を取得し、`helpers.analyzeResponse` で解析します。
    • レスポンスヘッダーをループして `Server:` で始まるヘッダーを探します。
    • 見つかった場合、`IScanIssue` インターフェースを実装した `CustomScanIssue` クラスのインスタンスを作成します。
    • `CustomScanIssue` のコンストラクタには、Issueに関する様々な情報(発生箇所URL、Issue名、詳細、深刻度、確信度、関連するHTTPメッセージなど)を渡します。
      • `httpMessages`: Issueの証拠となるリクエスト/レスポンスをリストで渡します。`callbacks.applyMarkers` を使うと、Issue箇所をハイライト表示するためのマーカーを付与できます(今回はリクエスト全体を対象としています)。
      • `detail`: Issueの詳細説明。HTMLタグを使って整形できます。`helpers.htmlEncode` を使ってユーザー提供データを安全に埋め込むことが推奨されます。
      • `severity`: “High”, “Medium”, “Low”, “Information”, “False positive” のいずれか。
      • `confidence`: “Certain”, “Firm”, “Tentative” のいずれか。
    • 作成したIssueオブジェクトを含むJavaのリスト (`ArrayList`) を返します。Burp Suiteはこのリストを受け取り、ScannerのIssue ActivityやTargetサイトマップのIssuesタブに表示します。
    • 何も検出しなかった場合は `None` または空のリストを返します。
  • `doActiveScan` メソッドはアクティブスキャン用ですが、今回は実装しないため `None` を返します。
  • `consolidateDuplicateIssues` メソッドは、同じIssueが繰り返し報告されるのを防ぐために実装します。ここでは単純にURLとIssue名が同じであれば重複とみなし `-1` を返しています。
  • `CustomScanIssue` クラスは `IScanIssue` インターフェースの各メソッドを実装します。コンストラクタで受け取った情報を返すだけのシンプルな実装です。`getIssueType` は、異なる種類のカスタムIssueを区別するためのユニークな整数値を返すように設計します。
  • リストの型: `doPassiveScan` が返すのは `java.util.List` 型である必要があります。そのため、PythonのリストではなくJavaの `ArrayList` を使用しています。

この拡張機能をロードした後、Burp Scannerでパッシブスキャンを実行するか、またはProxy経由で `Server` ヘッダーを含むレスポンスを受信すると、ScannerのIssue ActivityやTargetタブのIssuesに “Server Header Found” という情報レベルのIssueが報告されるようになります。✅

アクティブスキャンチェックの実装は、`doActiveScan` 内で `insertionPoint` オブジェクトを使ってペイロードを挿入したリクエストを `callbacks.makeHttpRequest` で送信し、そのレスポンスを分析するという流れになりますが、より複雑になります。

`IExtenderCallbacks` の `getHelpers()` メソッドで取得できる `IExtenderHelpers` オブジェクトは、拡張機能開発において頻繁に利用する便利なユーティリティメソッドを提供します。

いくつか代表的なものを紹介します。

メソッド説明
analyzeRequest(IHttpRequestResponse/byte[])
analyzeRequest(IHttpService, byte[])
HTTPリクエストのバイト配列または `IHttpRequestResponse` オブジェクトを解析し、ヘッダー、メソッド、URL、パラメータ、ボディ開始位置などの情報を持つ `IRequestInfo` オブジェクトを返します。パラメータの取得や変更も可能です。
analyzeResponse(byte[])HTTPレスポンスのバイト配列を解析し、ヘッダー、ステータスコード、MIMEタイプ、ボディ開始位置などの情報を持つ `IResponseInfo` オブジェクトを返します。
bytesToString(byte[])バイト配列をBurp Suiteの現在の設定に基づいた文字列に変換します。文字化けを防ぐために重要です。
stringToBytes(String)文字列をBurp Suiteの現在の設定に基づいたバイト配列に変換します。
urlDecode(String), urlEncode(String)URLデコードおよびエンコードを行います。バイト配列版もあります。
htmlDecode(String), htmlEncode(String)HTMLデコードおよびエンコードを行います。バイト配列版もあります。レポート表示などでXSSを防ぐために `htmlEncode` が役立ちます。
base64Decode(String), base64Encode(String)Base64デコードおよびエンコードを行います。バイト配列版もあります。
buildHttpMessage(List<String> headers, byte[] body)ヘッダーリストとボディのバイト配列から、完全なHTTPメッセージ(リクエストまたはレスポンス)のバイト配列を構築します。メッセージを改変する際によく使います。
buildHttpRequest(URL url)指定されたURLに対する基本的なGETリクエストのバイト配列を構築します。
toggleRequestMethod(byte[] request)HTTPリクエストのメソッドをGETとPOSTの間で切り替えます(パラメータの移動も試みます)。アクティブスキャンなどで役立つことがあります。
analyzeResponseVariations(...)
analyzeRequestVariations(...)
複数のレスポンス(またはリクエスト)間の差異を分析します。アクティブスキャンでペイロード挿入による変化を検出するのに使われます。

これらのヘルパーメソッドを使いこなすことで、HTTPメッセージの解析、操作、エンコード/デコードなどの一般的なタスクを簡単かつ正確に行うことができます。詳細は公式APIドキュメントを参照してください。

Burp Extension開発をスムーズに進めるためのヒントや注意点をいくつか紹介します。

  • デバッグ:
    • `print` 文 / `stdout`, `stderr`: 最も簡単なデバッグ方法です。Burp Suiteをコマンドラインから起動 (`java -jar burpsuite_*.jar`) すると、Pythonの `print` 文の出力や `callbacks.getStdout()/getStderr()` 経由の出力がコンソールに表示されます。
    • `callbacks.printOutput()` / `callbacks.printError()`: 拡張機能のOutput/Errorタブに出力します。Burp SuiteのUI上で確認できるので便利ですが、大量に出力するとUIが重くなる可能性があります。
    • デバッガ: PyDevなどのIDEと連携してリモートデバッグを行うことも可能ですが、設定がやや複雑になります。
  • エラー処理:
    • 拡張機能内で発生した例外は、適切に `try…except` で捕捉し、`callbacks.printError()` などでエラー情報を記録することが重要です。捕捉されない例外が発生すると、拡張機能が停止したり、Burp Suite全体が不安定になったりする可能性があります。
    • 特に `processHttpMessage` など頻繁に呼ばれるメソッド内でのエラー処理は丁寧に行いましょう。
  • パフォーマンス:
    • `IHttpListener` の `processHttpMessage` や `IScannerCheck` の `doPassiveScan` は、大量のメッセージに対して呼び出される可能性があります。これらのメソッド内での処理はできるだけ軽量に保ち、Burp Suite全体のパフォーマンスに影響を与えないように注意しましょう。
    • 時間のかかる処理を行う場合は、別スレッドで実行することも検討しますが、スレッドセーフティに注意が必要です。
  • APIのバージョン互換性:
    • Burp Suiteのバージョンアップに伴い、Extender APIが変更されることがあります。古いAPIが非推奨になったり、新しい機能が追加されたりします。特定のBurp Suiteバージョンに依存したコードを書いている場合、バージョンアップ後に動作しなくなる可能性があるので注意が必要です。
    • PortSwiggerのリリースノートやAPIドキュメントを定期的に確認しましょう。
  • Jythonの制約:
    • 前述の通り、JythonはCPythonと完全互換ではありません。特にC拡張モジュールに依存するライブラリは使用できません。
    • 標準ライブラリでも一部動作が異なる、または利用できないものがあります。
    • Python 3系のJythonも開発が進んでいますが、Burp Extension開発においてはまだPython 2系のJython (Jython 2.7.x) が広く使われている状況です(2024年時点)。使用するJythonのバージョンと互換性を意識する必要があります。
  • UI開発:
    • カスタムタブ (`ITab`, `IMessageEditorTab`) でUIを作成する場合、主にJava Swingを使用することになります。Swingの知識が必要になります。
    • UIの更新はEDT (Event Dispatch Thread) で行うのが原則です。Burp APIのコールバックメソッドがどのスレッドから呼ばれるかを意識し、必要に応じて `javax.swing.SwingUtilities.invokeLater` などを使用します。
  • 公式ドキュメントとコミュニティ:
    • Burp Extender API ドキュメント は最も重要な情報源です。各インターフェースやメソッドの詳細を確認できます。
    • PortSwiggerのサポートフォーラムや、Stack Overflowなどのコミュニティで他の開発者の質問やサンプルコードを探すのも有効です。
    • GitHubなどで公開されている既存のBurp Extensionのソースコードを読むのも非常に勉強になります。

この記事では、Python (Jython) を使ってBurp Extensionを開発するための基本的な手順と、主要なAPIインターフェース (`IBurpExtender`, `IHttpListener`, `IMessageEditorTabFactory`, `IScannerCheck`) の使い方を解説しました。

Burp Extension開発は、単純な作業の自動化から、高度なカスタムスキャンルールの実装、他のツールとの連携まで、アイデア次第で様々な可能性を秘めています。最初は難しく感じるかもしれませんが、簡単な拡張機能から少しずつ試していくことで、APIの使い方に慣れていくことができます。

今回紹介した内容は入門レベルですが、これを足がかりに、ぜひあなた自身のニーズに合わせた強力なBurp Extensionを作成してみてください。Happy Hacking! 💻✨

コメント

タイトルとURLをコピーしました