🐙

Burp Suite Collaboratorを作成・設定しよう。

に公開

今回はMontoya API回ではなく、Burp Suite Collaboratorに関する記事です。
Burp Suite Professional以上を持っている方のみのものとなります。

今回のゴール

チーム内だけで使用できるBurp Suite Collaboratorを立てることです。
Burp Suiteでその設定をしたあとで、Scannerがその設定を使えるようにすることも含みます。

公式ページ

Burp Suite Collaboratorとは

Burp SuiteのScanner機能を強力にサポートしてくれる公式外部ツールの一つです。
外部にCollaboratorサーバを立ててScanすることで、Collaboratorサーバに対して送信されたリクエストを検出することができます。
これによりBurp Suite単体では検出しづらい以下の脆弱性も検出することが可能です。

  • Log4Shell
  • Blind XSS
  • Blind SQL Injection
  • Second Order SQL Injection
  • Mail Injection

Collaboratorサーバが得られた情報をBurp Suiteにわたすのですが、既に診断対象サイトから飛んできているリクエストを元にしているため、Burp Suite単体より確度が高いのが特徴です。

Burp Suite Collaboratorサーバの作成

今回はAWSのEC2を使用して立てていきたいと思います。
Collaboratorサーバと言っても中身の実態はJARファイルがsystemctlで動いているだけなので、特別何か難しいことをする必要はありません。
ドメインを持っていることが必須条件になります。

準備するもの

  • ドメイン 1つ
  • AWS環境 1つ

Route53の設定

Collaboratorサーバは色々な機能を持ちます。
その中の一つがサブドメインの権威DNSサーバとしての機能です。
Route53には以下のレコードを追加します。

  1. サブドメインに関するAレコード(サーバのElastic IPに紐づけます)
  2. そのサブドメインのネームサーバとしてのAレコード(サーバのElastic IPに紐づけます)
  3. サブドメインのネームサーバが2.であるというNSレコード

EC2の設定

サーバはなんでもいいです。UbuntuでもCentOSでも好きなものを立ててください。これ以降はUbuntuをベースに記載していきます。

$ sudo apt clean -y && sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove -y
$ sudo apt install openjdk-21-jre-headless authbind

JARファイルの配置など

Collaboratorサーバに必要なJARファイルは、自分のPCでBurp Suite ProfessionalをインストールしたときにPC内に配置されます。
burpsuite_pro.jarなどの名前で存在しているので、頑張って探してください。
rloginなどの好きな方法でサーバ上にそのJARファイルを持ってきます。

$ mv ./burpsuite_pro.jar /path/to
$ java -jar /path/to/burpsuite_pro.jar --collaborator-server
Ctrl+C //動作確認のための起動。

あとで証明書更新作業の自動化を紹介しますが、サーバの中身も自動化したい方はsystemctlについてのファイルは特に作成したユーザがburpsuite_pro.jarを動かすことができるだけのものを作成してください。

configファイルの配置

JARファイルを置いたディレクトリと同一階層に、configファイルを配置します。
公式がサンプルコンフィグファイルを置いてくれているので、それを元に記載します。
変更する必要があるもので重要な部分は以下のとおりです。

  • serverDomain
    • サーバのドメイン名(Aレコードで設定)
  • ssl
    • このあとで設定するワイルドカード証明書のフルパス
  • localAddress
    • Ec2のプライベートIPv4アドレス
  • publicAddress
    • Ec2のパブリックIPv4アドレス
  • dns
    • ns1のAレコードに関する情報

DomainはもとよりIPAddressと、SSL周りを変更しておきます。

{
    "serverDomain": "COLLABORATOR.DOMAIN.COM",
    "workerThreads": 10,
    "interactionLimits": {
        "http": 8192,
        "smtp": 8192
    },
    "eventCapture": {
        "localAddress": ["10.20.0.159", "127.0.0.1"],
        "publicAddress": "10.20.0.159",
        "http": {
            "ports": 80
        },
        "https": {
            "ports": 443
        },
        "smtp": {
            "ports": [25, 587]
        },
        "smtps": {
            "ports": 465
        },
        "ssl": {
            "certificateFiles" : [
                "/path/to/private.key",
                "/path/to/public.key"
            ]
        }
    },
    "polling": {
        "localAddress": "127.0.0.1",
        "publicAddress": "10.20.0.159",
        "http": {
            "port": 9090
        },
        "https": {
            "port": 9443
        },
        "ssl": {
            "certificateFiles": [
            "/path/to/private.key",
            "/path/to/public.key"
            ]
        }
    },
    "metrics": {
        "path": "jnaicmez8",
        "addressWhitelist": ["10.10.23.0/24"]
    },
    "dns": {
        "interfaces": [{
                "name": "ns1",
                "localAddress": "10.20.0.159",
                "publicAddress": "98.87.76.55"
            }],
        "ports": 53
    },
    "logLevel": "INFO",
    "customDnsRecords" : [
        {
            "label" : "_acme-challenge",
            "type" : "TXT",
            "record" : "jsd3Ew2nign7svGT",
            "ttl" : 60
        }
    ],
    "customHttpContent": [
        {
            "path": "/",
            "contentType": "text/html",
            "base64Content": "PCFkb2N0eXBlIGh0bWw+Cgo8aHRtbCBsYW5nPSJlbiI+CjxoZWFkPgogIDxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KICA8dGl0bGU+RXhhbXBsZSBQYWdlPC90aXRsZT4KCiAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJjc3MvbXlzdHlsZXNoZWV0LmNzcyI+CjwvaGVhZD4KPGJvZHk+CjxoMT5XZWxjb21lIHRvIG15IGV4YW1wbGUgcGFnZTwvaDE+CjxwPlRoYW5rcyBmb3IgdmlzaXRpbmcgbXkgZXhhbXBsZSBwYWdlLjwvcD4KPC9ib2R5Pgo8L2h0bWw+"
        },
        {
            "path": "/info/readme.txt",
            "contentType": "text/plain",
            "base64Content": "VGhpcyBpcyB0aGUgcmVhZG1lIGZpbGUgZm9yIG15IGNvbGxhYm9yYXRvciBzZXJ2ZXIu"
        }
    ]
}

以降、Burp Suite Collaboratorを立ち上げる際は以下のような形でコマンドを実行します。

java -jar /path/to/file.jar --collaborator-server --collaborator-config=myconfig.config

Let's Encryptでワイルドカード証明書の発行

NSレコードを消す

Burp Suite CollaboratorはカスタムDNSレコードを設定することができますが、のちの証明書更新時にコンフィグファイルを編集する必要が出てきます。
レコード更新でいいと思う場合は、コレ以降に記載しているものを参考にしていただけたら良いですし、レコードの履歴が汚れるのを好まない場合などは、コンフィグファイル内のカスタムDNSレコードを編集するなどして自動化してください。

個人的には、NSレコードを消すのが一番わかりやすくて手っ取り早いと思います。他のレコードを間違って消さないようにしましょう。

NSレコードを消した後、5分ぐらい待ってから次の手順に行きましょう。

Let's Encryptでワイルドカード証明書を発行する

sudo apt install certbot python3-certbot-dns-route53

Let's EncryptとRoute53用のプラグインを導入します。Route53に関連するものを必要とするのは、ワイルドカード証明書を発行する際には、DNS-01認証が必要だからです。

sudo certbot certonly \
  --dns-route53 \
    -d "WILDCARD DOMAIN NAME(e.g. *.example.com)" -d DOMAIN NAME(e.g. example.com) \
  --non-interactive \
  --agree-tos \
  --email your@email.com

ワイルドカードを含むドメインはダブルクォーテーションで括りましょう。

Collaboratorのドメインも対象にしているのは、Burp Suite側から情報を取得するpolling portに接続する際にもSSL証明書が必要だからです。別々に分けたいよ!っていう人は別々に分けて取得してください。

NSレコードを追加する

ワイルドカード証明書が発行できたので、Collaboratorに設定していたNSレコードを元に戻します。

configファイルにSSL証明書の設定をする

取得したSSL証明書をCollaboratorサーバのconfigファイルに記載していきます。
証明書のPermissionなどが気になる場合は、一度JARファイルがあるディレクトリにコピーしたあとで、chownなどを使用してCollaborator用の証明書とするとよいでしょう。

Burp Suiteから確認する

テストをしてオールグリーンであれば問題ありません。

StepFunctionで証明書更新の自動化

上の作成した内容を自動化します。特に人間がやる理由もないので、エラーを吐いたときに見に行くようにします。
DNS-01認証の有効期間は90日間です。そのため、定期的に実行して証明書が有効であることを示す必要があります。

IAMロールを作成する

Route53, SSMに関する権限を使用する必要があります。
最小権限の法則に従って、必要な権限を必要なリソースに対して必要な分だけ追加してください。

StepFunctionを作成する

基本のStep Functionはこのような流れのものを作成します。
このStep Funcitonをそのまま使用してもよいですが、Slack通知機能などは作成していないためエラー発生時もユーザがわからない可能性があります。
Call HTTPS APIsなどを使ってSlackなどへ通知してあげる機能は別途作成してください。
また、以下の要素はご自身のが使用しているAWS上で表示されているIDなどに変更してください。

  • HOSTZONEID
  • COLLABORATOR.DOMAIN.NAME.
  • i-0000INSTANCEID
  • sudo certbot renew --deploy-hook='/path/to/beforeRenew.sh'
    • /path/to/beforeRenew.shでは、発行した証明書の移動や、パーミッションの変更などを記載するといいと思います。
{
  "Comment": "A description of my state machine",
  "StartAt": "ListResourceRecordSets",
  "States": {
    "ListResourceRecordSets": {
      "Type": "Task",
      "Arguments": {
        "HostedZoneId": "HOSTZONEID"
      },
      "Resource": "arn:aws:states:::aws-sdk:route53:listResourceRecordSets",
      "Next": "ChangeResourceRecordSets",
      "Output": {
        "collaboratorRecord": "{% $states.result.ResourceRecordSets[Name='COLLABORATOR.DOMAIN.NAME.' and Type='NS'] %}"
      }
    },
    "ChangeResourceRecordSets": {
      "Type": "Task",
      "Arguments": {
        "ChangeBatch": {
          "Changes": [
            {
              "Action": "DELETE",
              "ResourceRecordSet": "{% $states.input.collaboratorRecord %}"
            }
          ]
        },
        "HostedZoneId": "HOSTZONEID"
      },
      "Resource": "arn:aws:states:::aws-sdk:route53:changeResourceRecordSets",
      "Next": "Wait (1)",
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "Pass"
        }
      ]
    },
    "Wait (1)": {
      "Type": "Wait",
      "Seconds": 30,
      "Next": "GetChange"
    },
    "GetChange": {
      "Type": "Task",
      "Arguments": {
        "Id": "{% $states.input.ChangeInfo.Id %}"
      },
      "Resource": "arn:aws:states:::aws-sdk:route53:getChange",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "Wait",
          "Condition": "{% ($states.input.ChangeInfo.Status) = (\"INSYNC\") %}"
        },
        {
          "Next": "Wait (1)",
          "Condition": "{% ($states.input.ChangeInfo.Status) = (\"PENDING\") %}"
        }
      ],
      "Default": "Pass"
    },
    "Pass": {
      "Type": "Pass",
      "Next": "Fail"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 300,
      "Next": "SendCommand"
    },
    "SendCommand": {
      "Type": "Task",
      "Arguments": {
        "DocumentName": "AWS-RunShellScript",
        "InstanceIds": [
          "i-0000INSTANCEID"
        ],
        "Parameters": {
          "commands": [
            "sudo certbot renew --deploy-hook='/path/to/beforeRenew.sh'"
          ]
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:sendCommand",
      "Next": "Wait (2)",
      "Output": {
        "CommandId": "{% $states.result.Command.CommandId %}"
      }
    },
    "Wait (2)": {
      "Type": "Wait",
      "Seconds": 30,
      "Next": "GetCommandInvocation"
    },
    "GetCommandInvocation": {
      "Type": "Task",
      "Arguments": {
        "CommandId": "{% $states.input.CommandId %}",
        "InstanceId": "i-0000INSTANCEID"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCommandInvocation",
      "Next": "Choice (2)",
      "Output": {
        "Status": "{% $states.result.Status %}"
      }
    },
    "Choice (2)": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "ChangeResourceRecordSets (1)",
          "Condition": "{% ($states.input.Status)=(\"Success\") %}"
        },
        {
          "Next": "Wait (2)",
          "Condition": "{% (($states.input.Status) = (\"Pending\") or ($states.input.Status) = (\"InProgress\") or ($states.input.Status) = (\"Delayed\")) %}"
        }
      ],
      "Default": "Pass"
    },
    "ChangeResourceRecordSets (1)": {
      "Type": "Task",
      "Arguments": {
        "ChangeBatch": {
          "Changes": [
            {
              "Action": "CREATE",
              "ResourceRecordSet": {
                "Name": "COLLABORATOR.DOMAIN.NAME.",
                "ResourceRecords": [
                  {
                    "Value": "NS RECORD VALUE"
                  }
                ],
                "Ttl": 86400,
                "Type": "NS"
              }
            }
          ]
        },
        "HostedZoneId": "HOSTZONEID"
      },
      "Resource": "arn:aws:states:::aws-sdk:route53:changeResourceRecordSets",
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "Pass"
        }
      ],
      "Next": "Wait (3)"
    },
    "Wait (3)": {
      "Type": "Wait",
      "Seconds": 30,
      "Next": "GetChange (1)"
    },
    "GetChange (1)": {
      "Type": "Task",
      "Arguments": {
        "Id": "{% $states.input.ChangeInfo.Id %}"
      },
      "Resource": "arn:aws:states:::aws-sdk:route53:getChange",
      "Next": "Choice (1)"
    },
    "Choice (1)": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "成功",
          "Condition": "{% ($states.input.ChangeInfo.Status) = (\"INSYNC\") %}"
        },
        {
          "Next": "Wait (3)",
          "Condition": "{% ($states.input.ChangeInfo.Status) = (\"PENDING\") %}"
        }
      ],
      "Default": "Pass"
    },
    "成功": {
      "Type": "Succeed"
    },
    "Fail": {
      "Type": "Fail"
    }
  },
  "QueryLanguage": "JSONata"
}

試運転する

実際に動かしてみましょう。画像のようにすべて緑になれば大丈夫です。

定期実行する。

このままでは最初を手動で実行して上げる必要があるため、EventBridgeから定期的に実行してもらうようにします。
Step Funcitonの管理画面上でEventBridgeルールを作成を押して、スケジュールを作成します。


90日の有効期間なので、rateとして任意の日数を開けてください。

ターゲットの設定でStep Functionを指定し、下のプルダウンから先ほど作成したものを指定します。

Burp Suite上で設定・確認をする。

Collaboratorの設定情報を、Burp Suiteに入力します。
上の設定ファイルからポート番号を変更していない場合、以下の画像のような設定になります。
その後、Run health check...をクリックして、すべてSuccessが表示されたら設定は完了です。
Project Settingsに含まれる要素なので、プロジェクトファイルとして保存しておくことで以降は手動で実行せずとも取得できます。

設定ができたあとはScannerを実行しても良いサイト(DockerでWordPressをローカルに立てるなど)に対してScanを実行してください。
LoggerでScannerが送信したリクエストを見て、Collaboratorのドメインが挿入されていたら正常に動作しています。

以上です。

Discussion