hacktricks/pentesting-web/race-condition.md

27 KiB
Raw Blame History

レースコンディション


Trickestを使用して、世界で最も進んだコミュニティツールを駆使したワークフローを簡単に構築し自動化します。
今すぐアクセス:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

RCの悪用

RCを悪用する主な問題は、リクエストが非常に短い時間差通常は>1msで並行して処理される必要があることです。以下のセクションでは、これを可能にするためのさまざまな解決策が提案されています。

シングルパケット攻撃HTTP/2/ ラストバイト同期HTTP/1.1

HTTP2では、1つのTCP接続で2つのリクエストを送信できますHTTP/1.1では順番に行う必要があります)。
単一のTCPパケットの使用は、ネットワークジッターの影響を完全に排除しますので、これは明らかにレースコンディション攻撃にも有効です。しかし、サーバーサイドのジッターのおかげで、2つのリクエストでは信頼性のあるレース攻撃には不十分です。これは、CPU競合などの制御不能な変数によって引き起こされるアプリケーションのリクエスト処理時間の変動です。

しかし、HTTP/1.1の「ラストバイト同期」技術を使用すると、各リクエストの大部分のデータを事前に送信し、ごくわずかな断片を保留しておき、**1つのTCPパケットで20〜30のリクエストを「完了」**させることが可能です。

各リクエストの大部分を事前に送信するには:

  • リクエストにボディがない場合は、すべてのヘッダーを送信しますが、END_STREAMフラグは設定しません。END_STREAMが設定された空のデータフレームを保留します。
  • リクエストにボディがある場合は、ヘッダーとボディデータの最後のバイトを除くすべてを送信します。最後のバイトを含むデータフレームを保留します。

次に、最終フレームの送信を準備します:

  • 初期フレームが送信されたことを確認するために100ms待ちます。
  • TCP_NODELAYが無効になっていることを確認します - 最終フレームをバッチ処理するためにナグルアルゴリズムが不可欠です。
  • ローカル接続を温めるためにピングパケットを送信します。これを行わないと、OSのネットワークスタックは最初の最終フレームを別のパケットに配置します。

最後に、保留していたフレームを送信します。Wiresharkを使用して、それらが単一のパケットに着陸したことを確認できるはずです。

{% hint style="info" %} 特定のサーバー上の静的ファイルには機能しないことに注意してくださいが、静的ファイルはRC攻撃には関係ありません。 {% endhint %}

この技術を使用すると、ネットワークジッターに関係なく、20〜30のリクエストをサーバーに同時に到着させることができます

ターゲットアーキテクチャへの適応

多くのアプリケーションはフロントエンドサーバーの背後にあり、これらは一部のリクエストを既存の接続を介してバックエンドに転送し、他のリクエストには新しい接続を作成することを決定する場合があります。

その結果、リソースに一度にアクセスできるのは単一のスレッドのみであるようなロックメカニズムなど、アプリケーションの動作に一貫性のないリクエストタイミングを帰属させないことが重要です。また、フロントエンドのリクエストルーティングは通常、接続ごとに行われるため、攻撃を実行する前に接続を温めることでリクエストタイミングを滑らかにすることができるかもしれません(これは実際の攻撃を開始する前にいくつかのリクエストを送信することです)。

セッションベースのロックメカニズム

一部のフレームワークは、偶発的なデータ破損を防ぐために、何らかの形のリクエストロックを使用しています。例えば、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シングルパケット攻撃複数のエンドポイント: 1つのエンドポイントにリクエストを送り、その後RCEをトリガーするために他のエンドポイントに複数のリクエストを送る必要がある場合、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)

Raw BF

以前の研究より前には、RCを引き起こすためにできるだけ早くパケットを送信しようとするペイロードがいくつか使用されていました。

  • Repeater: 前のセクションの例を確認してください。
  • Intruder: リクエストIntruderに送り、オプションメニュー内でスレッド数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は、攻撃者がアクセスすることを意図されていなかった状態を悪用することができるマシン状態のサブステートを悪用しますが、攻撃者がそれにアクセスするための小さなウィンドウがあります。

  1. 潜在的な隠された興味深いサブステートを予測する

最初のステップは、それに書き込むか、またはそれからデータを読み取り、そのデータを何か重要なことに使用するすべてのエンドポイントを特定することです。例えば、ユーザーは登録、プロファイル編集、パスワードリセットの開始、パスワードリセットの完了によって変更されるデータベーステーブルに格納されるかもしれません。

衝突を引き起こす可能性が低いエンドポイントを除外するために、3つの重要な質問を使用できます。各オブジェクトと関連するエンドポイントについて、次のことを尋ねます

  • 状態はどのように保存されますか?

サーバー側の永続的なデータ構造に保存されたデータは、悪用に理想的です。一部のエンドポイントは、JWTをメールで送信するパスワードリセットのように、その状態を完全にクライアント側で保存します - これらは安全にスキップできます。

アプリケーションはしばしばいくつかの状態をユーザーセッションに保存します。これらはしばしばサブステートに対してある程度保護されています - 後で詳しく説明します。

  • 編集していますか、それとも追加していますか?

既存のデータを編集する操作例えば、アカウントの主要なメールアドレスを変更するは、衝突の可能性が十分にありますが、既存のデータに単に追加する操作例えば、追加のメールアドレスを追加するは、Limit-overrun攻撃以外には脆弱である可能性が低いです。

  • 操作は何に基づいていますか?

ほとんどのエンドポイントは、ユーザー名、パスワードリセットトークン、またはファイル名などの「キー」を使用して検索される特定のレコードに対して操作します。成功した攻撃には、同じキーを使用する2つの操作が必要です。例えば、2つのありそうなパスワードリセットの実装を想像してみてください

  1. 手がかりを探る

この時点で、興味深いエンドポイントに対していくつかのRC攻撃を開始し、通常のものと比較して予期しない結果を見つけようとします。期待される応答からの逸脱、例えば1つ以上の応答の変更、またはセッションの可視的な変更のような二次効果は、何かが間違っていることを示す手がかりになる可能性があります。

  1. コンセプトを証明する

最後のステップは、コンセプトを証明し、それを実行可能な攻撃に変えることです。

一連のリクエストを送信すると、早い段階のリクエストペアが脆弱なエンドステートを引き起こす可能性がありますが、後のリクエストがそれを上書き/無効にし、最終的な状態は悪用できないものになります。このシナリオでは、不要なリクエストをすべて排除する必要があります - ほとんどの脆弱性を悪用するには2つで十分です。ただし、リクエストを2つに減らすと攻撃のタイミングが敏感になるため、攻撃を複数回試行するか、自動化する必要があるかもしれません。

タイムセンシティブ攻撃

時にはレースコンディションを見つけることができないかもしれませんが、正確なタイミングでリクエストを送信する技術は他の脆弱性の存在を明らかにすることができます。

その一例は、高解像度のタイムスタンプがセキュリティトークンを生成するために暗号学的に安全なランダム文字列の代わりに使用される場合です。

タイムスタンプを使用してランダム化されるパスワードリセットトークンを考えてみてください。この場合、同じトークンを使用する2つの異なるユーザーのパスワードリセットをトリガーすることが可能かもしれません。必要なのは、同じタイムスタンプを生成するようにリクエストをタイミングすることだけです。

{% hint style="warning" %} 例えば前述の状況を確認するには、同時に2つのパスワードリセットトークンを要求する(シングルパケット攻撃を使用して)ことができ、それらが同じかどうかを確認します。 {% endhint %}

このラボの例をチェックしてください。

隠されたサブステートのケーススタディ

支払い & アイテム追加

このラボをチェックして、店で支払いを行い、それに対して追加料金を支払う必要がないアイテムを追加する方法を見てください。

他のメールを確認する

このアイデアは、メールアドレスを確認し、同時に異なるものに変更することで、プラットフォームが新しく変更されたものを検証するかどうかを確認することです。

2つのメールアドレスに変更する Cookieベース

このライトアップによると、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でもレースコンディションを悪用します

参考文献

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥


世界で最も進んだコミュニティツールを駆使してワークフローを簡単に構築し自動化するためにTrickestを使用してください。
今すぐアクセス:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}