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では順次送信する必要があります)。
1つのTCPパケットの使用は、ネットワークの揺らぎの影響を完全に排除するため、レースコンディション攻撃にも潜在的な可能性があります。ただし、2つのリクエストでは信頼性のあるレース攻撃には十分ではありません。これは、サーバーサイドの揺らぎによるものです。これは、CPUの競合などの制御できない変数によって引き起こされるアプリケーションのリクエスト処理時間の変動です。

しかし、HTTP/1.1の 'ラストバイト同期' 技術を使用すると、各リクエストからわずかなフラグメントを保留し、その後、1つのTCPパケットで20〜30のリクエストを完了することができます。

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

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

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

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

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

{% hint style="info" %} 特定のサーバーの静的ファイルでは機能しませんが、静的ファイルはレースコンディション攻撃には関係ありません。しかし、静的ファイルはRC攻撃には関係ありません。 {% endhint %}

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

ターゲットアーキテクチャに適応する

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

そのため、アプリケーションの動作による一貫性のないリクエストタイミングをロックメカニズムなどのアプリケーションの動作に帰することは重要ではありません。また、フロントエンドのリクエストルーティングは通常、接続ごとに行われるため、攻撃を実行する前にサーバーサイドの接続ウォーミングを実行することで、リクエストのタイミングをスムーズにすることができる場合があります(これは、実際の攻撃を開始する前にいくつかのリクエストを送信するだけです)。

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

一部のフレームワークでは、リクエストロックとしての形式を使用して、データの誤った破損を防ぐことを試みています。たとえば、PHPのネイティブセッションハンドラモジュールは、1つのセッションあたり1つのリクエストのみを処理します。

このような動作を見つけることは非常に重要です。なぜなら、これによって簡単に悪用できる脆弱性が隠されてしまう可能性があるからです。すべてのリクエストが順次処理されていることに気付いた場合は、それぞれのリクエストを異なるセッショントークンを使用して送信してみてください。

レート制限またはリソース制限の乱用

接続のウォーミングが効果がない場合、この問題に対するさまざまな解決策があります。

Turbo Intruderを使用すると、クライアント側で短い遅延を導入することができます。ただし、これには実際の攻撃リクエストを複数のTCPパケットに分割する必要があるため、シングルパケット攻撃技術を使用することはできません。その結果、高ジッターターゲットでは、どのような遅延を設定しても攻撃が確実に機能しない可能性があります。

代わりに、一般的なセキュリティ機能を乱用することでこの問題を解決することができるかもしれません。

Webサーバーは、リクエストがあまりにも速く送信された場合に処理を遅延させることがよくあります。ダミーリクエストを大量に送信してレート制限またはリソース制限を意図的にトリガーすることで、適切なサーバーサイドの遅延を引き起こすことができるかもしれません。これにより、遅延実行が必要な場合でも、シングルパケット攻撃が実行可能になります。

{% hint style="warning" %} この技術についての詳細は、https://portswigger.net/research/smashing-the-state-machineの元のレポートを参照してください。 {% endhint %}

攻撃の例

  • Tubo Intruder - HTTP2シングルパケット攻撃1エンドポイント:リクエストをTurbo IntruderExtensions -> Turbo Intruder -> Send to Turbo Intruder)に送信できます。リクエスト内の**%sのようなブルートフォースしたい値を変更し、ドロップダウンからexamples/race-single-packer-attack.py**を選択します。

異なる値を送信する場合は、クリップボードからワードリストを使用する次のコードに変更できます。

passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')

{% hint style="warning" %} ウェブが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)
  • また、Burp Suiteの新しい「Send group in parallel」オプションを使用して、Repeaterでも利用できます。
  • limit-overrunの場合、グループに同じリクエストを50回追加するだけです。
  • connection warmingの場合、ウェブサーバーの非静的な部分にいくつかのリクエストをグループの先頭追加することができます。
  • 2つのサブステートステップで1つのリクエストともう1つのリクエストの間の処理を遅延させる場合、両方のリクエストの間に追加のリクエストを追加することができます。
  • multi-endpointのRCの場合、隠れた状態に移動するリクエストを送信し、その後その隠れた状態を利用するリクエストを50回送信します。

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

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の方法論

制限超過/TOCTOU

これは最も基本的なタイプの競合状態であり、アクションを実行できる回数を制限する場所に現れる 脆弱性が存在します。たとえば、ウェブストアで同じ割引コードを複数回使用することです。このレポートこのバグ****に非常に簡単な例があります。

この種の攻撃にはさまざまなバリエーションがあります。

  • ギフトカードを複数回利用する
  • 製品に複数回評価を付ける
  • 口座残高を超えて現金を引き出すまたは送金する
  • 単一のCAPTCHAソリューションを再利用する
  • 反ブルートフォースのレート制限をバイパスする

隠れたサブステート

他の最も複雑なRCは、攻撃者がアクセスすることを意図されていなかった状態を悪用できるマシンのサブステートを利用しますが、攻撃者がアクセスするための小さなウィンドウがあります。

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

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

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

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

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

アプリケーションは、ユーザーセッションに一部の状態を保存することがよくあります。これらは通常、サブステートに対してある程度保護されています。後で詳しく説明します。

  • 編集または追加していますか?

既存のデータを編集する操作(アカウントの主なメールアドレスを変更するなど)は、十分な衝突の可能性がありますが、既存のデータに追加するだけの操作(追加のメールアドレスを追加するなど)は、制限超過攻撃以外の脆弱性に対して脆弱ではありません。

  • 操作は何にキー付けられていますか?

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

  1. 手がかりを探る

この時点で、潜在的に興味深いエンドポイントに対していくつかのRC攻撃を実行して、通常のものとは異なる結果を見つけようとします。応答の変化、または異なるメールの内容やセッションの目に見える変化など、予想される応答からの逸脱は、何かが間違っていることを示す手がかりになる可能性があります。

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

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

一括のリクエストを送信すると、初期のリクエストペアが脆弱なエンドステートをトリガーする場合がありますが、後のリクエストがそれを上書き/無効にし、最終的な状態は攻撃できないものになります。このシナリオでは、不要なリクエストをすべて削除する必要があります。ほとんどの脆弱性を悪用するには2つのリクエストが十分です。ただし、2つのリクエストにすると、攻撃はタイミングに敏感になるため、攻撃を複数回リトライするか、自動化する必要がある場合があります。

タイムセンシティブ攻撃

場合によっては、競合状態を見つけることができないかもしれませんが、正確なタイミングでリクエストを送信するための技術は、他の脆弱性の存在を明らかにすることができます。

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

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

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

このラボの例を確認してください。

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

商品の支払いと追加

このラボをチェックして、支払いを行い、追加の商品を追加する方法を確認してください。追加の商品には支払いが必要ありません。

他のメールの確認

アイデアは、メールアドレスを確認し、同時に別のメールアドレスに変更し、プラットフォームが新しいメールアドレスを確認するかどうかを確認することです。

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

この解説によると、Gitlabはこの方法で乗っ取られる可能性がありました。なぜなら、Gitlabは1つのメールの電子メール確認トークンを他のメールに送信する可能性があるからです。

これについては、このラボもチェックしてください。

隠れたデータベースの状態/確認バイパス

2つの異なる書き込みが使用されてデータベース情報を追加する場合、最初のデータがデータベースに書き込まれた後、**2番目のデータ

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/LinkedIn/GitHubなどの一般的なログインで、以下のようなページが表示されます: "Application <InsertCoolName> があなたの情報にアクセスすることを希望しています。許可しますか?"

authorization_codeにおける競合状態

問題は、それを受け入れ、自動的に**authorization_codeを悪意のあるアプリケーションに送信するときに発生します。その後、このアプリケーションはOAuthサービスプロバイダー内の競合状態を悪用して、authorization_codeから複数のAT/RT**認証トークン/リフレッシュトークン)を生成します。基本的には、あなたがアプリケーションがデータにアクセスすることを許可したことを悪用して、複数のアカウントを作成します。その後、アプリケーションにデータへのアクセスを許可しなくなると、1つのAT/RTのペアは削除されますが、他のものは有効のままです。

Refresh Tokenにおける競合状態

有効なRTを取得した後、それを悪用して複数のAT/RTを生成しようとすることができます。そして、ユーザーが悪意のあるアプリケーションに対してアクセス許可をキャンセルした場合でも、複数のRTは有効のままです。

WebSocketsにおける競合状態

WS_RaceCondition_PoCでは、JavaでWebソケットメッセージを並列に送信して、Webソケットでも競合状態を悪用するPoCを見つけることができます。

参考文献

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


Trickestを使用して、世界で最も高度なコミュニティツールによって強化されたワークフローを簡単に構築し、自動化します。
今すぐアクセスを取得してください:

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