🥞

HTTPレスポンスと各要素を取得しよう

に公開

初期設定

この記事を参考に、環境を整えてください。

この記事のゴール

Burp SuiteのExtentionコンソール内にHTTPレスポンスの中の要素を表示させることです。
すべてを紹介するととんでもない長さになるので、基本的なものを取得していきたいと思います。

公式サンプルコード

公式サンプルコードはとても優秀なので、公式のコードをオーバーライドする形で記載していきます。

JavaDoc

取得できるレスポンスの中身

レスポンスの中身は基本的に全て取得できると考えて良いです。
Body、Headerの中でおよそ取得したいと考えるものはすべて取得できると考えていいでしょう。

基本構成

ファイル構成や大まかな構造は公式コードをオーバーライドする形で進めます。基本的に各値を取得する際に変更が必要なのはrequestResponseHandler.javaの方のみなので、以降のコードはそちら側のみ記載します。

App.java

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package org.httpRequestResponseHandler;

import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;


public class App implements BurpExtension{

    @Override
    public void initialize(MontoyaApi api) {
        api.extension().setName("Httpリクエスト・レスポンスの各要素を取得するExtention");

        // Burp Suiteを通過するHttpリクエスト・レスポンスを取得する。
        api.http().registerHttpHandler(new httpRequestResponseHandler(api));
    }

}

requestResponseHandler.java
こちら側にレスポンスに関するコードを色々書いて、取得して改ざんしたりします。
リクエストは前回の記事を参照してください。

package org.httpRequestResponseHandler;

import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.Annotations;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.logging.Logging;

import static burp.api.montoya.http.handler.RequestToBeSentAction.continueWith;
import static burp.api.montoya.http.handler.ResponseReceivedAction.continueWith;

class httpRequestResponseHandler implements HttpHandler {
    private final Logging logging;

    public httpRequestResponseHandler(MontoyaApi api) {
        this.logging = api.logging();
    }

    @Override
    public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
        Annotations annotations = requestToBeSent.annotations();

        // リクエストがスコープ内かどうかを判定する。
        if(requestToBeSent.isInScope()){
            // コンソール内に取得したリクエストを表示する。
            logging.logToOutput(requestToBeSent.headers().toString());
        }
        return continueWith(requestToBeSent,annotations);
    }

    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        return continueWith(responseReceived,annotations);
    }

}

レスポンスを取得する前に

さて、ではレスポンスを取得していきましょう。以降は記事の長さを短くするために handleHttpResponseReceivedメソッドのみの記載とします。その他は上のコードと同じです。

レスポンスヘッダを取得する

レスポンスヘッダをログに出力する

前回の記事で説明した logToOutput()を使用して出力します。
他に使用するMontoya APIは以下のです。

  • responseReceived.headers()

このExtentionがProxyしたHttpレスポンスはresponseReceivedに入っているため、ここからはこのObjectの中身を詳細に見ていこうという形になります。


@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
    Annotations annotations = responseReceived.annotations();

    logging.logToOutput(responseReceived.headers().toString());

    return continueWith(responseReceived,annotations);
}

これを使用して取得したヘッダは以下のように表示されます。

特定のレスポンスヘッダとその値をログに出力する

今度は特定のレスポンスヘッダを取得しましょう。

使用するMontoya APIは以下のとおりです。各APIはそれぞれの値を取得してくれます。hasHeader()は、レスポンス内にそのパラメータが存在するかどうかを判定してくれます。
また、一番最後のように、レスポンスヘッダ名を文字列で指定してあげることもできます。これにより、ヘッダに格納されているCSRFトークンなどを取得することができますね。

  • responseReceived.statusCode()
  • responseReceived.inferredMimeType()
  • responseReceived.mimeType()
  • responseReceived.statedMimeType()
  • responseReceived.markers()
  • responseReceived.headerValue("Server")

この他にも色々あるので、公式から確認してみてください


    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        // コンソール内にステータスコードを表示する。
        logging.logToOutput(Short.toString(responseReceived.statusCode()));

        // コンソール内にレスポンスの内容から推測できるMIMETYPEを表示する。
        logging.logToOutput(responseReceived.inferredMimeType().toString());

        // コンソール内にBurp Suiteが決めたMIMETYPEを表示する。
        logging.logToOutput(responseReceived.mimeType().toString());

        // コンソール内にレスポンスヘッダに記載されているMIMETYPEを表示する。
        logging.logToOutput(responseReceived.statedMimeType().toString());

        // コンソール内にマーカーを表示する。
        logging.logToOutput(responseReceived.markers().toString());

        // コンソール内にレスポンスヘッダに記載されているServerを表示する。
        if (responseReceived.hasHeader("Server")) {
            logging.logToOutput(responseReceived.headerValue("Server"));
        }

        return continueWith(responseReceived,annotations);
    }

これを使用して取得したヘッダは以下のように表示されます。

Cookieを取得する

脆弱性診断で取得したいものの1つにCookieがあります。レスポンスヘッダに取得したCookieを表示しましょう。

使用するAPIは以下のとおりです。

  • responseReceived.cookies()
  • responseReceived.hasCookie()
  • responseReceived.cookieValue()

この他にもあるので、公式JavaDocから確認してみてください。

    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        // Cookieをすべて取得する(メモリ番地…?)。
        logging.logToOutput(responseReceived.cookies().toString());

        // 特定のCookieが存在する場合、それを取得する。
        if(responseReceived.hasCookie("cookieForBurp")){
            logging.logToOutput(responseReceived.cookieValue("cookieForBurp"));
        }
        return continueWith(responseReceived,annotations);
    }

これを使用して取得したヘッダは以下のように表示されます。

レスポンスボディを取得する

レスポンスボディをログに出力する

最後にリクエストボディを取得して、その内容をログに出力します。
使用するMontoya APIは以下のとおりです。

  • requestToBeSent.bodyToString()
    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        // コンソールログにレスポンスボディを表示する。
        logging.logToOutput(responseReceived.bodyToString());

        return continueWith(responseReceived,annotations);
    }

これを使用して取得したレスポンスボディは以下のように表示されます。最初の空配列は、レスポンスにCookieが存在しない場合の出力です。

レスポンスボディのJSONをパースしてその値を表示する

Burp Suiteを使っている方々にとって一番やりたいことと言えば、JSONのパースでしょう。
Burp Suiteの仕組みだけでは、JSONの取り回しがしにくいです。他のExtentionでは正規表現で対応していることが多いです。CSRFトークンの取得などは、他のExtentionにもありますが欲しい機能でしょう。
そんなJSONをパースして表示してみましょう。
Burp SuiteのJsonUtils()はパターンを記載することでJSONをパースできるのですが、JSONの中身の値を直接指定する際にa.b.cみたいな形で書くときに、追加で以下のような制約があります。

  • 配列は []を使って表す。
    • [i]の形で表記すると、配列のi番目の要素を取得する。
    • []の形で表記すると、配列の一番最後の要素を取得する。

JSONに関する処理はまた後でコラム的な形でまとめます。

今回対象とするレスポンスに記載されているJSONは以下のものを使用します。この場合、テストを取得したい場合は、[0].titleと記述します。

[
    {
        "title": "テスト",
        "uuid": "019575b7-ba98-758c-8a74-56687d1da6aa"
    },
    {
        "title": "テスト1",
        "uuid": "01957062-4030-72b6-9105-3527cd8326ca"
    },
    {
        "title": "テスト2",
        "uuid": "019566a1-9c72-76d2-a5fe-f3190e6f070d"
    },
    {
        "title": "テスト3",
        "uuid": "01956186-51eb-7569-affa-b22482c8ab90"
    },
    {
        "title": "テスト4",
        "uuid": "01956182-ef7e-7431-867c-fbf3a0def321"
    }
]

JSONの中の要素を取得するコードは以下のような形で記載します。今回はカッコの中を何も記載していないので配列内にある最後の要素の中のuuidを取得しています。これがとれるとCSRFトークンなどのものも取ってくることができますね。

package org.httpRequestResponseHandler;

import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.Annotations;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.logging.Logging;
import burp.api.montoya.utilities.json.JsonUtils;

import static burp.api.montoya.http.handler.RequestToBeSentAction.continueWith;
import static burp.api.montoya.http.handler.ResponseReceivedAction.continueWith;

class httpRequestResponseHandler implements HttpHandler {
    private final Logging logging;
    private final JsonUtils jsonUtils;

    public httpRequestResponseHandler(MontoyaApi api) {
        this.logging = api.logging();
        this.jsonUtils = api.utilities().jsonUtils();
    }


    @Override
    public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent){
        Annotations annotations = requestToBeSent.annotations();
        return continueWith(requestToBeSent, annotations);
    }

    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        // コンソールログにレスポンスボディを表示する。
        if (jsonUtils.isValidJson(responseReceived.bodyToString())) {
            logging.logToOutput(jsonUtils.read(responseReceived.bodyToString(), "[].uuid"));
        }
        return continueWith(responseReceived,annotations);
    }
}

これを使用して取得したJSONは以下のように表示されます。無事に一番最後のuuidだけ取得することができています。

そのレスポンスに対応したリクエストを取得する

そんなことできんのっていうことができます。Burp Suite本体がmessageIdの照合を行っているのですが、試してみたい方はrequestToBeSent.messageId()responseReceived.messageId()を比べてみると嬉しくなれます。
ということで、使用するAPIは以下のとおりです。

  • responseReceived.initiatingRequest()

このAPIでは、リクエストに関するAPIをくっつけて使用することができます。
method()path()を使用することで、リクエストメソッドやリクエストパスを取得することができます。

    @Override
    public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
        Annotations annotations = responseReceived.annotations();

        // コンソールにレスポンスに対応したリクエストメソッドとリクエストパスを取得して表示する。
        logging.logToOutput(
                responseReceived.initiatingRequest().method() + " " + responseReceived.initiatingRequest().path());
        // コンソールにレスポンスボディを表示する。
        logging.logToOutput(responseReceived.bodyToString());

        return continueWith(responseReceived,annotations);
    }

これを使用して取得したレスポンスに対応したリクエストは以下のように表示されます。日本語表示されないのは何故なんでしょうか…。

まとめ

レスポンスの取得・改ざんは基本的にAPIを使用して行うことができます。
ヘッダはだいたい以下のAPIを使用することで取得することができます。一番下のものを使用してヘッダ名を直接指定することで、オリジナルのヘッダなどを取得することができます。

  • responseReceived.statusCode()
  • responseReceived.inferredMimeType()
  • responseReceived.mimeType()
  • responseReceived.statedMimeType()
  • responseReceived.markers()
  • responseReceived.headerValue("Server")

Cookieを取得する際は、以下のAPIで存在確認と値の取得をすることができます。

  • responseReceived.cookies()
  • responseReceived.hasCookie()
  • responseReceived.cookieValue()

レスポンスボディは以下のAPIを使用することで取得することができます。

  • requestToBeSent.bodyToString()

また、レスポンスボディに含まれるJSONをパースして特定の値を取得するためには以下のような記述をします。以下の例は、配列内の一番最後のuuidの値を取得するものです。

  • jsonUtils.read(responseReceived.bodyToString(), "[].uuid")

また、レスポンスに対応したリクエストを取得する必要がある場合は、以下のAPIを使用することでリクエストの情報を引っ張ってくることができます。

  • responseReceived.initiatingRequest()

今度からはリクエスト・レスポンスの改ざんや追加、Burp Suiteの機能のON/OFFなどの記事を書いていこうと思います。

Discussion