0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【BurpSuite】Python Scripterを使用したJSONパラメータの引き継ぎ方法

0
Last updated at Posted at 2026-02-15

概要

BurpSuiteでWebアプリケーションやAPIの脆弱性診断を行う際に、パラメータを引き継ぎたい場面があります。
基本的にはBurpSuiteにもともと備わっているMacrosと、Session handling rulesを使用することで引き継ぎ可能ですが、引き継ぎたいパラメータがJSON形式の場合、この機能だけでは引き継ぎができません。
BurpSuiteの拡張機能「Python Scripter」を使用すればこれが実現できるので、本記事ではその拡張機能の使い方を記載します。

※Pythonを書かずにGUIの画面操作だけで完結したい場合は、【BurpSuite】JSONパラメータの引き継ぎ方法を参照ください。

※HTMLフォーム送信におけるパラメータを引き継ぎたい場合は、【BurpSuite】パラメータの引き継ぎ方法を参照ください。

目的

  • BurpSuiteのRepeaterでリクエストBを送信した際に、リクエストAで得たJSONパラメータを、リクエストBに引き継ぐ

前提

デモとして下記仕様とします。

  • エンドポイントBで正常なレスポンスを得たい場合、エンドポイントAで返却された文字列が必要である
  • 正常な文字列でエンドポイントBを叩くと、200OKとなる
  • 不正な文字列でエンドポイントBを叩くと、400エラーでNGとなる

image.png
【正常】エンドポイントAで取得した文字列を、エンドポイントBに引き継ぐ

image.png
【不正】不正な文字列でエンドポイントBを叩くと、400エラーでNGと返却される

手順

拡張機能「Python Scripter」をインストール

BApp Storeで「Python Scripter」と検索し、installボタンを押下してください。Installedタブでチェックがはいった状態になっていればOKです。
image.png
「Python Scripter」をインストールする
image.png
「Python Scripter」を有効化する

jython の設定

Python Scripterの動作には、jythonが必要です。
https://www.jython.org/download.html から「jython-standalone-x.x.x.jar」をダウンロードします。
settingで「jython」と検索し、ダウンロードしたファイルを設定します。
image.png
jythonの設定

これで準備は整いました。
Python Scriptsタブを開きます。
Python記載箇所にスクリプトを記載していきます。
本スクリプトを有効化したいときは、「Enabled」にチェックします。
新しいスクリプトを記載したら、「Compile」が反映には必須です。
image.png

Python Script

今回のスクリプトの仕様は以下とします。

  • エンドポイントBをRepeater、Intruder、Scannerで送信したときに発火させる
  • RepeaterなどでエンドポイントBへ送信されたときに、スクリプトでエンドポイントAに送信し、そこで得られた文字列をエンドポイントBに渡す

以下スクリプトで、デモが動作します。
使用する際は、定数らをよしなに書き換えてください。

import sys,re,datetime,locale
from burp import IBurpExtenderCallbacks

"""
診断対象であるエンドポイントB(target)で200OKを得るには、エンドポイントA(pre)で返却される文字列が必要である。
エンドポイントBをリピーターなどで送信した際に、エンドポイントAを送信して得られた文字列をエンドポイントBに与えるスクリプト
"""

TARGET_PASS = '/api/endpointB'
PRE_REQUEST_CONTENTS = (
    "GET /api/endpointA HTTP/2\r\n"
    "Host: example.com\r\n\r\n"
)
PRE_RESPONSE_BODY_REGEX = '"result":"([0-9a-z-]*)"'
TARGET_REQUEST_REPLACE_VALUE = '{"uuid":"%s"}'

if toolFlag & (
    IBurpExtenderCallbacks.TOOL_REPEATER |
    IBurpExtenderCallbacks.TOOL_INTRUDER |
    IBurpExtenderCallbacks.TOOL_SCANNER
):
    if messageIsRequest:
        
        try:
            service = messageInfo.getHttpService()
            request = messageInfo.getRequest()
            requestInfo = helpers.analyzeRequest(service, request)
            headers = requestInfo.getHeaders()
            requestUrl = requestInfo.getUrl().toString()

            if TARGET_PASS in requestUrl:
                print "## Get target request"
                
                pre = callbacks.makeHttpRequest(service, PRE_REQUEST_CONTENTS)
                print "## Send pre request"
                    
                preResponseBytes = pre.getResponse()
                preResponseInfo = helpers.analyzeResponse(preResponseBytes)
                preResponseBody = preResponseBytes[preResponseInfo.getBodyOffset():]
                preResponseBodyStr = helpers.bytesToString(preResponseBody)
                    
                preResponseMessage = re.search(PRE_RESPONSE_BODY_REGEX, preResponseBodyStr)
                targetValue = preResponseMessage.group(1)
                    
                targetBody = TARGET_REQUEST_REPLACE_VALUE % targetValue
                targetHeaders = []
                for h in headers:
                    if not h.lower().startswith("Content-Length"):
                        targetHeaders.append(h)
        
                target = helpers.buildHttpMessage(targetHeaders, targetBody)
                messageInfo.setRequest(target)
                    
                print "## FINNISH\r\n"
                
        except Exception as e:
            print "ERROR:", e

解説

1

from burp import IBurpExtenderCallbacks

Burpが提供しているインターフェイスを使用できるようにします。
Repeater、Intruder、Scannerで送信したかを条件にいれるために使用しています。

2

if messageIsRequest:

今フックしている通信がリクエストかどうかを見ています。
これを記載しないと、おそらく無限ループになります。

3

service = messageInfo.getHttpService()
request = messageInfo.getRequest()
requestInfo = helpers.analyzeRequest(service, request)

これでフックしたリクエストを取得しています。
メソッドでリクエスト情報を色々取得できます。

例)

  • ヘッダー取得:
    • requestInfo.getHeaders()
  • メソッド取得:
    • requestInfo.getMethod()
  • URLを文字列で取得:
    • requestInfo.getUrl()
  • パラメータ一覧取得:
    • requestInfo.getParameters
  • HTTPプロトコル取得:
    • requestInfo.getHttpVersion()

4

pre = callbacks.makeHttpRequest(service, PRE_REQUEST_CONTENTS)

Pythonスクリプトで通信を発生させています。
今回はエンドポイントAに送信してリクエストを得る必要があったので、スクリプト上で通信を発生させています。上記は新しい通信を発生させていますが、一方で下記は新しい通信は発生させていません。

target = helpers.buildHttpMessage(targetHeaders, targetBody)
messageInfo.setRequest(target)

ここでは、フックしたリクエストを書き換えて送信しています。

5

preResponseMessage = re.search(PRE_RESPONSE_BODY_REGEX, preResponseBodyStr)
targetValue = preResponseMessage.group(1)

ここでは、エンドポイントAで取得したレスポンスボディを、正規表現で取得したい文字列だけ抜き出しています。

PRE_RESPONSE_BODY_REGEX = '"result":"([0-9a-z-]*)"'

定数で正規表現を定義しています。この正規表現で抜き出したい文字列の部分にかっこ()をいれることで、preResponseMessage.group(1) で抜き出せます。

6

TARGET_REQUEST_REPLACE_VALUE = '{"uuid":"%s"}'
targetBody = TARGET_REQUEST_REPLACE_VALUE % targetValue

ここで、抜き出した文字列をセットしてます。

7

targetHeaders = []
for h in headers:
    if not h.lower().startswith("Content-Length"):
        targetHeaders.append(h)

これはHTTPヘッダのContent-Lengthを削除するための処理です。
既存ヘッダから Content-Length ヘッダだけ除外しています。
Content-Length が無ければ自動で正しい値を付与してくれます。

参考文献

Python Scripter - PortSwigger
GitHub - PortSwigger/python-scripter

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?