👀

フロントエンド開発にかかせないBurp

2024/10/16に公開

便利ツール

私たちは日々の開発の中で様々な便利ツールを使います。
その中でもBurpは多くの機能を持ち、エンジニアにとってかかせないツールとなっています。

今回の記事ではフロントエンド開発においてBurpがどのように役立つかを紹介します。

Burpとは

https://portswigger.net/burp

BurpはPortSwiggerにより提供されているツールで、Professional、Enterprise Editionなどの種類があります。中でもCommunity Editionは無料で提供されており、誰でも公式サイトからダウンロードすることができます。

Burpプロダクト一覧

では早速Burpの使い方を紹介していきます。

Proxy

ProxyはBurpを使う上で最もベーシックな機能になります。

準備

Burpのタブ画像

  1. Burpを立ち上げ、タブからProxyを選択
  2. Open browserをクリックし、ブラウザを立ち上げる
  3. 解析したいページを開く

上記のようにBurpで立ち上げた専用のブラウザで開くことでBurpがProxyサーバーのような役割をします。

リクエストを観察する

Burpの左上にあるIntercept Offというボタンを押すとInterceptをONにでき、リクエストの詳細を一つずつ観察することができます。

例えば私のGitHubのプロフィールページを開いてみます。
最初のリクエストは以下のようになっており、この状態では上図の2.Burpでリクエストが止まっています。Forwardボタンを押すことでこのリクエストを実際に3.Origin Serverに投げることができます。

GET /fujiyamaorange HTTP/2
Host: github.com
Cookie: _octo=GH1.1.22222222222.3333333333; logged_in=no; _gh_sess=hoge; preferred_color_mode=dark; tz=Asia%2FTokyo
Pragma: no-cache
Cache-Control: no-cache
Sec-Ch-Ua: "Not;A=Brand";v="24", "Chromium";v="128"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
(略)

そしてこの後のリクエストによりページのJSやCSS、画像などが読み込まれていきます。

あるリクエストに対してDropボタンを押すと2.Burpでリクエストが破棄され、3.Origin Serverにリクエストが届かなくなります。

先ほどのページで最初のリクエスト以外の全てをDropすると以下のような画面になり、ブラウザのテーマや画像などが正しく読み込まれていないことがわかります。

アセットを読み込めないGitHubプロフィールページ

レスポンスを書き換える

先ほどリクエストをDropする処理を紹介しましたが、Burpにはレスポンスを書き換える機能もあります。

  1. 対象のレスポンスを右クリックで選択
  2. Do interceptResponse to this requestを選択
  3. 編集画面でリクエストを書き換える

Interceptの方法

ここでは先ほどのGitHubページにランディングした際の最初のリクエストを書き換えてみます。

Before
HTTP/2 200 OK
Server: GitHub.com
Date: Tue, 08 Oct 2024 13:39:03 GMT
Content-Type: text/html; charset=utf-8
Vary: X-Requested-With, X-PJAX-Container, Turbo-Frame, Turbo-Visit, Accept-Encoding, Accept, X-Requested-With
Etag: W/"542620f67bf562062bed456aff24a41a"
Cache-Control: max-age=0, private, must-revalidate
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-Xss-Protection: 0
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin

Content-Typeをtext/htmlからapplication/jsonに変更してみます。

After(application/jsonに変更)
HTTP/2 200 OK
Server: GitHub.com
Date: Tue, 08 Oct 2024 13:39:03 GMT
Content-Type: application/json; charset=utf-8
Vary: X-Requested-With, X-PJAX-Container, Turbo-Frame, Turbo-Visit, Accept-Encoding, Accept, X-Requested-With
Etag: W/"542620f67bf562062bed456aff24a41a"
Cache-Control: max-age=0, private, must-revalidate
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
X-Xss-Protection: 0
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin

上記のようにContent-Typeを書き換えるとレスポンス内容が正しく読み込まれず以下のような表示になります。

application/jsonで表示したGitHubプロフィールページ

今回はContent-Typeを書き換えましたが、この機能はフロントエンドにおいて状態のエッジケースを実際のリクエストと共に確認したい時に非常に役に立ちます。

ぜひAPIレスポンスを書き換えて、エラーハンドリングを充実させましょう!

Repeater

上記に加え、BurpにはRepeaterという便利な機能があります。

Repeaterはその名の通り、特定のリクエストを何度も送ることができる機能です。しかし単に同じリクエストを送るのではなく、リクエストを編集しつつレスポンスの変化を観察できます。

今回は例としてGoogleのGitHub Organizationをフォローしてみようと思います。

Google GitHub Organization

Burpで立ち上げたブラウザに自分のGitHubアカウントでログインし、以下を実行します。

  1. InterceptをONにし、フォローボタンを押下
  2. 対象のリクエストを右クリックでSend to Repeaterを選択

フォローするリクエストは以下のように構成されていました。

POST /users/follow?target=google HTTP/2
Host: github.com
Cookie: _octo=GH1.1.1883337129.1699597551; preferred_color_mode=dark; tz=Asia%2FTokyo; _device_id=<device_id>; saved_user_sessions=<saved_user_sessions>; user_session=<user_session>; __Host-user_session_same_site=<user_session_same_site>; tz=Asia%2FTokyo; color_mode=%7B%22color_mode%22%3A%22dark%22%2C%22light_theme%22%3A%7B%22name%22%3A%22light%22%2C%22color_mode%22%3A%22light%22%7D%2C%22dark_theme%22%3A%7B%22name%22%3A%22dark%22%2C%22color_mode%22%3A%22dark%22%7D%7D; logged_in=yes; dotcom_user=fujiyamaorange; _gh_sess=<hogehoge>
Content-Length: 334
Pragma: no-cache
Cache-Control: no-cache
(略)

パラメータを変更する

では上記のリクエストのクエリパラメータであるtargetmoneyforwardに変更してみます。

HTTP/2 200 OK
Server: GitHub.com
Date: Wed, 09 Oct 2024 10:53:43 GMT
Content-Type: application/json; charset=utf-8
Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame
Etag: W/"3c2d8b0a6d72f80fa653b1ac0d6230b2"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _gh_sess=<_gh_sess=>; path=/; secure; HttpOnly; SameSite=Lax
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
(略)

そうするとなんと200 OKが返ってきており別のブラウザで確認してもきちんとフォローできていることがわかります。

Money Forwardをフォローした画像

許可されないリクエストを送る

では先ほどのtargetを自分のアカウントであるfujiyamaorangeに変更してリクエストを送ってみます。

HTTP/2 422 Unprocessable Entity
Server: GitHub.com
Date: Wed, 09 Oct 2024 10:59:11 GMT
Content-Type: application/json; charset=utf-8
Vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame
Cache-Control: no-cache
Set-Cookie: _gh_sess=<_gh_sess>; path=/; secure; HttpOnly; SameSite=Lax
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Frame-Options: deny
X-Content-Type-Options: nosniff
(略)

すると今回は422 Unprocessable Entityが返ってきました。おもしろいですね。
こういったように特定のフォーマットを編集したリクエストを送りそのレスポンスを調べることができます。

Intruder

では次にIntruderを紹介していきます。

今回は攻撃対象として、Burpの公式ドキュメントに載っているサイトを使います。

今まで行ってきたようにBurpからブラウザを立ち上げ以下のような専用のサイトを開きます。

https://<hogehoge>.web-security-academy.net/login

攻撃対象のサイト

以下のようにしてログインのリクエストをIntruderに送ります。

  1. 適当なUsernamePasswordを入力しログインを試みる
  2. HTTP Historyからログインに使用しているリクエストを特定する
  3. 対象のリクエストを右クリック&Sent to Intruderを選択

今回対象となったリクエストです。

POST /login HTTP/2
Host: <hogehoge>.web-security-academy.net
Cookie: session=§<session>§
Content-Length: 31
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="129", "Not=A?Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Accept-Language: ja
Origin: https://<hogehoge>.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://<hogehoge>.web-security-academy.net/login
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

username=§hoge§&password=§password§

画面右にあるAuto §を選択するとBurpがリクエストのパラメータになっているところを自動で判定してくれます。(§で挟まれている部分)

上記の場合はCookie、bodyのusernameとpasswordがパラメータであると判定されました。

セクション記号の書かれているボタン

usernameを特定する

では早速Intruderを使ってサイトを攻撃してみます。
まずはユーザー名から判定したいので下のようにusernameのみをパラメータとします。

username=§hoge§&password=password

Payloadsタブへ移ると以下のようにパラメータに入れる値をセットできます。

Payloadsタブ

今回はあらかじめ用意された以下のような辞書を使います。

carlos
root
admin
test
guest
info
adm
mysql
user
(略)
au
auction
austin
auth
auto
autodiscover

辞書がセットできたところで右上のStart attackを押下すると全てのリクエストを走らせることができます。

Community Editionでは以下のような警告が出ますが、リクエストがthrottleされるだけで機能を使用することはできるので安心してください。

Intruderは制限されている

処理が終了すると以下のように結果が別ウィンドウで表示されます。
そして各リクエストに対してどんなレスポンスが返ってきたのかを1つずつ確認できます。

Intruderの結果

ここで注目したいのが、右にあるLengthです。全てが3248になっているように見えますが、ソートしてみると1つだけ3250のリクエストを見つけることができました。

Lengthでソートした結果

もうお分かりだと思いますが、このリクエストだけ表示されるエラーメッセージが異なっています。

<p class=is-warning>Incorrect password</p>

つまりこのメッセージは対象のusernameは存在するということを意味しています。
たびたび警鐘されていることですが、このエラー文言は脆弱です。
本来は「ユーザー名またはパスワードが間違っています」というような文言にすべきです。

ここまででユーザー名を特定することができました。

passwordを特定する

上記によりユーザー名がarizonaであることがわかったので、今度はpasswordを特定していきます。

以下のようにpasswordがパラメータになるように設定し、あらかじめ用意した辞書を用いて再度攻撃を行います。

username=arizona&password=§password§

処理が終了すると以下のようになりました。こちらも明らかにLengthの異なるレスポンスがあることがわかります。

パスワード攻撃のログ

ということで無事、パスワードが12345678であることがわかりました。

HTTP/2 302 Found
Location: /my-account?id=arizona
Set-Cookie: session=UB4T1xNxv2PzpIz9Hu9O7kQF7AW5ahqu; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 0

こうした一連のチェックをリリース前のサービスに行うことでシステムの安全性を高めることができます。

まとめ

いくつかBurpの機能を紹介してきましたが、これを見て便利だと感じてもらえたら嬉しいです。
まだまだ紹介しきれていない機能がたくさんあるので私も引き続き使い倒し、理解を高めていこうと思います。

ここまで読んでくださりありがとうございました。🎉

Discussion