Race conditions

PortSwigger Academy – Race conditions

Continua il percorso di apprendimento suggerito da “PortSwigger Academy”.

Le race conditions  sono un tipo comune di vulnerabilità strettamente correlata ai difetti della logica aziendale. Si verificano quando i siti Web elaborano le richieste contemporaneamente senza adeguate garanzie. Ciò può portare a più thread distinti che interagiscono con gli stessi dati contemporaneamente, risultando in una “collisione” che provoca comportamenti non intenzionali nell’applicazione. Un attacco di race condition utilizza richieste attentamente cronometrate per causare collisioni intenzionali e sfruttare questo comportamento non intenzionale a scopi dannosi.

Il periodo di tempo durante il quale è possibile una collisione è noto come “race window”. Questa potrebbe essere la frazione di secondo tra due interazioni con il database, per esempio.

Come altri difetti logici, l’impatto di una race condition dipende fortemente dall’applicazione e dalla funzionalità specifica in cui si verifica.

In questa sezione, imparerai come identificare e sfruttare diversi tipi di race conditions. Ti verrà spiegato come gli strumenti integrati di Burp Suite possono aiutarti a superare le sfide dell’esecuzione di attacchi classici, oltre a una metodologia collaudata che ti consente di rilevare nuove classi di race conditions in processi nascosti in più fasi. Questi vanno ben oltre i limiti di cui potresti avere già familiarità.

PortSwigger Research

Come al solito, PortSwigger ha anche fornito una serie di laboratori deliberatamente vulnerabili che puoi usare per praticare ciò che hai imparato in modo sicuro contro obiettivi realistici. Molti di questi si basano sulla ricerca originale di Portswigger, presentata per la prima volta a Black Hat USA 2023.

Per maggiori dettagli, dai un’occhiata al white paper di accompagnamento: Smashing the state machine: The true potential of web race conditions

Limitare le race conditions di sovraccarico

Il tipo più noto di race condition consente di superare una sorta di limite imposto dalla logica aziendale dell’applicazione.

Ad esempio, considera un negozio online che ti consente di inserire un codice promozionale durante il checkout per ottenere uno sconto una tantum sul tuo ordine. Per applicare questo sconto, l’applicazione può eseguire i seguenti passaggi di alto livello:

  1. Controllare di non aver già usato questo codice.
  2. Applicare lo sconto sul totale dell’ordine.
  3. Aggiornare il record nel database per riflettere il fatto che ora hai utilizzato questo codice.

Se in seguito si tenta di riutilizzare questo codice, i controlli iniziali eseguiti all’inizio del processo dovrebbero impedirti di farlo:

Ora considera cosa accadrebbe se un utente che non ha mai applicato questo codice di sconto prima ha provato ad applicarlo due volte quasi esattamente nello stesso momento:

Come puoi vedere, l’applicazione passa attraverso un sotto-stato temporaneo; Cioè, uno stato che entra e poi si rialza prima che l’elaborazione della richiesta sia completa. In questo caso, il sub-stato inizia quando il server inizia a elaborare la prima richiesta e termina quando aggiorna il database per indicare che hai già utilizzato questo codice. Questo introduce una piccola finestra di gara durante la quale puoi rivendicare ripetutamente lo sconto tutte le volte che vuoi.

Ci sono molte varianti di questo tipo di attacco, tra cui:

  • Riscattare una carta regalo più volte.
  • Valutare un prodotto più volte.
  • Prelievo o trasferimento di contanti superiori al saldo del tuo conto.
  • Riutilizzare un’unica soluzione CAPTCHA.
  • Bypassare il rate limit di anti-brute-force.

I sovraccarichi limite sono un sottotipo dei cosiddetti difetti “time-of-check to time-of-use” (TOCTOU). Più tardi in questo argomento, esamineremo alcuni esempi di vulnerabilità delle condizioni di gara che non rientrano in nessuna di queste categorie.

Rilevare e sfruttare il limite di sovraccarico delle race conditions con Burp Repeater

Il processo di rilevamento e sfruttamento del sovraccarico delle race conditions è relativamente semplice. In termini di alto livello, tutto ciò che devi fare è:

  1. Identificare un endpoint single-use o rate-limited che ha un qualche tipo di impatto sulla sicurezza o altro utile.
  2. Emettere più richieste a questo endpoint in rapida successione per vedere se è possibile sovraccaricare questo limite.

La sfida principale è il cronometraggio delle richieste in modo che almeno due finestre di race si allineino, causando una collisione. Questa finestra è spesso solo di millisecondi e può essere ancora più breve.

Anche se si inviano tutte le richieste esattamente allo stesso tempo, in pratica ci sono vari fattori esterni incontrollabili e imprevedibili che influenzano quando il server elabora ogni richiesta e in quale ordine.

Burp Suite ​​aggiunge potenti nuove funzionalità al Repeater di Burp che  consentono di inviare facilmente un gruppo di richieste parallele in modo da ridurre notevolmente l’impatto di uno di questi fattori, vale a dire il jitter di rete. Burp regola automaticamente la tecnica che utilizza per adattarsi alla versione HTTP supportata dal server:

  • per HTTP/1, utilizza la classica tecnica di sincronizzazione dell’ultimo byte;
  • per HTTP/2, utilizza la tecnica di attacco a pacchetto singolo, dimostrata per la prima volta da Portswigger Research at Black Hat USA 2023.

L’attacco a pacchetto singolo consente di neutralizzare completamente l’interferenza dal jitter di rete utilizzando un singolo pacchetto TCP per completare contemporaneamente le richieste 20-30.

Sebbene puoi spesso utilizzare solo due richieste per attivare un exploit, l’invio di un gran numero di richieste come questa aiuta a mitigare la latenza interna, nota anche come jitter lato server. Ciò è particolarmente utile durante la fase di scoperta iniziale. Tratteremo questa metodologia in modo più dettagliato.

Per saperne di più

Per i dettagli su come utilizzare le nuove funzionalità di Burp Repeater per inviare più richieste in parallelo, consultare Sending requests in parallel.

Per una visione tecnica dei meccanici sottostanti dell’attacco a pacchetto singolo e uno sguardo più dettagliato alla metodologia, dai un’occhiata al white paper di accompagnamento: Smashing the state machine: The true potential of web race conditions.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun

Rilevare e sfruttare il sovraccarico del limite delle race conditions con Turbo Intruso

Oltre a fornire supporto nativo per l’attacco a pacchetto singolo nel Repeater di Burp, è stata anche migliorata l’estensione del Turbo Intruder per supportare questa tecnica. Puoi scaricare l’ultima versione dallo store BAPP.

Turbo Intruder richiede una certa competenza in Python, ma è adatto a attacchi più complessi, come quelli che richiedono più tentativi, tempistica delle richieste sfalsate o un numero estremamente elevato di richieste.

Per usare l’attacco a pacchetti singolo in Turbo Intruder:

  1. Assicurarsi che l’obiettivo supporti HTTP/2. L’attacco a pacchetto singolo è incompatibile con HTTP/1.
  2. Impostare le opzioni di configurazione engine=Engine.BURP2 e concurrentConnections=1  per il motore di richiesta.
  3. Quando fai la coda delle richieste, raggrupparle assegnandole a un gate usando l’argomento gate metodo engine.queue().
  4. Per inviare tutte le richieste in un determinato gruppo, aprire il rispettivo gate con il metodo engine.openGate().
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                            concurrentConnections=1,
                            engine=Engine.BURP2
                            )
    
    # queue 20 requests in gate '1'
    for i in range(20):
        engine.queue(target.req, gate='1')
    
    # send all requests in gate '1' in parallel
    engine.openGate('1')

Per maggiori dettagli, consultare il modello race-single-packet-attack.py fornito nella directory di esempi predefiniti di Turbo Intruder.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-bypassing-rate-limits

Sequenze in più fasi nascoste

In pratica, una singola richiesta può avviare un’intera sequenza in più fasi dietro le quinte, passando l’applicazione attraverso più stati nascosti che entrano e poi si ripetono prima che l’elaborazione della richiesta sia completa. Ci riferiremo a questi come “sottocampi”.

Se è possibile identificare una o più richieste HTTP che causano un’interazione con gli stessi dati, è possibile abusare potenzialmente di questi sottocampi per esporre variazioni sensibili al tempo dei tipi di difetti logici che sono comuni nei flussi di lavoro in più fasi. Ciò consente di sfruttare le condizioni di gara che vanno ben oltre il limite.

Ad esempio, potresti avere familiarità con i flussi di lavoro di autenticazione multi-fattore difettosi (multi-factor authentication – MFA) che consentono di eseguire la prima parte del login utilizzando credenziali note, quindi navigare direttamente all’applicazione tramite navigazione forzata, aggirando efficacemente MFA.

Il seguente pseudo-codice dimostra come un sito Web potrebbe essere vulnerabile a una variazione di gara di questo attacco:

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

Come puoi vedere, questa è in realtà una sequenza in più fasi nell’arco di una singola richiesta. Ancora più importante, passa attraverso un sotto-stato in cui l’utente ha temporaneamente una sessione di accesso valida, ma l’MFA non è ancora applicata. Un utente malintenzionato potrebbe potenzialmente sfruttare questo inviando una richiesta di accesso insieme a una richiesta a un endpoint sensibile e autenticato.

In seguito esamineremo altri esempi di sequenze in più fasi nascoste e sarai in grado di esercitarti a sfruttarli nei laboratori interattivi. Tuttavia, poiché queste vulnerabilità sono abbastanza specifiche dell’applicazione, è importante comprendere prima la metodologia più ampia che dovrai applicare per identificarle in modo efficiente, sia nei laboratori che in realtà.

Metodologia

Per rilevare e sfruttare sequenze multi-fase nascoste, PortSwiger raccomanda la seguente metodologia, che è riassunta dal white paper Smashing the state machine: The true potential of web race conditions di PortSwigger Research.

1 – Prevedere potenziali collisioni

Testare ogni endpoint non è pratico. Dopo aver mappato il sito di destinazione come normalmente, puoi ridurre il numero di endpoint che devi testare ponendoti le seguenti domande:

  • questa sicurezza endpoint è critica? Molti endpoint non toccano la funzionalità critica, quindi non vale la pena testare;
  • c’è qualche potenziale collisione? Per una collisione di successo, in genere è necessario due o più richieste che attivano operazioni nello stesso record. Ad esempio, considera le seguenti variazioni di un’implementazione di reimpostazione della password:

Con il primo esempio, è improbabile che la richiesta di reimpostazione della password parallela per due utenti diversi causi una collisione in quanto si traduce in modifiche a due record diversi. Tuttavia, la seconda implementazione consente di modificare lo stesso record con richieste per due utenti diversi.

2 – Sonda alla ricerca di indizi

Per riconoscere degli indizi, devi prima confrontare come l’endpoint si comporta in condizioni normali. Puoi farlo in Burp Repeater raggruppando tutte le tue richieste e utilizzando l’opzione Send group in sequence (separate connections). Per ulteriori informazioni, consultare Sending requests in sequence.

Successivamente, invia lo stesso gruppo di richieste contemporaneamente utilizzando il single-packet attack (o last-byte sync se HTTP/2 non è supportato) per ridurre al minimo il jitter di rete. Puoi farlo in Burp Repeater selezionando l’opzione Send group in parallel. Per ulteriori informazioni, consultare Sending requests in parallel. In alternativa, è possibile utilizzare l’estensione Turbo Intruder, disponibile dal negozio BAPP.

Tutto può essere un indizio. Basta cercare una qualche forma di deviazione da ciò che hai osservato durante il benchmarking. Ciò include un cambiamento in una o più risposte, ma non dimenticare i second-order effects come diversi contenuti e-mail o una modifica visibile nel comportamento dell’applicazione in seguito.

3 – Dimostra il concetto

Prova a capire cosa sta succedendo, rimuovi richieste superflue e assicurati di poter ancora replicare gli effetti.

Le condizioni di gara avanzate possono causare primitivi insolite e uniche, quindi il percorso al massimo impatto non è sempre immediatamente ovvio. Può aiutare a pensare a ciascuna condizione di gara come a una debolezza strutturale piuttosto che a una vulnerabilità isolata.

Per una metodologia più dettagliata, dai un’occhiata al white paper completo Smashing the state machine: The true potential of web race conditions

Condizioni di gara multi-endpoint

Forse la forma più intuitiva di queste condizioni di gara sono quelle che coinvolgono l’invio di richieste a più endpoint contemporaneamente.

Pensa al classico difetto logico nei negozi online in cui aggiungi un oggetto al tuo carrello, paghi, quindi aggiungi più oggetti al carrello prima di effettuare un forced-browsing alla pagina di conferma dell’ordine.

Una variazione di questa vulnerabilità può verificarsi quando la convalida dei pagamenti e la conferma dell’ordine vengono eseguite durante l’elaborazione di un’unica richiesta. Lo stato dell’ordine potrebbe assomigliare a questo:

In questo caso, puoi potenzialmente aggiungere più oggetti al tuo carrello durante la finestra di gara tra quando il pagamento è convalidato e quando l’ordine è finalmente confermato.

Allineare multi-endpoint race windows

Durante il test per le condizioni di gara multi-endpoint, è possibile riscontrare problemi che cercano di allineare le finestre di gara per ogni richiesta, anche se le invii tutte esattamente allo stesso tempo utilizzando la single-packet technique.

Questo problema comune è causato principalmente dai seguenti due fattori:

  • ritardi introdotti dall’architettura di rete, ad esempio, potrebbe esserci un ritardo ogni volta che il server front-end stabilisce una nuova connessione al back-end. Il protocollo utilizzato può anche avere un impatto importante;
  • ritardi introdotti dall’elaborazione specifica per endpoint, endpoint diversi variano intrinsecamente nei loro tempi di elaborazione, a volte significativamente, a seconda delle operazioni che attivano.

Fortunatamente, ci sono potenziali soluzioni alternative per entrambi questi problemi.

Connection warming

I ritardi di connessione back-end di solito non interferiscono con gli attacchi delle condizioni di gara perché in genere ritardano le richieste parallele allo stesso modo, quindi le richieste rimangono in sintonia.

È essenziale essere in grado di distinguere questi ritardi da quelli causati da fattori specifici dell’endpoint. Un modo per farlo è “riscaldando” la connessione con una o più richieste insignificanti per vedere se ciò appiana i tempi di elaborazione rimanenti. Nel Repeater di Burp, puoi provare ad aggiungere una richiesta GET per la homepage all’inizio del tab group, quindi utilizzando l’opzione Send group in sequence (single connection).

Se la prima richiesta ha ancora un tempo di elaborazione più lungo, ma il resto delle richieste viene ora elaborato all’interno di una finestra breve, è possibile ignorare il ritardo apparente e continuare a testare come normalmente.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-multi-endpoint

Se si vedono ancora tempi di risposta incoerenti su un singolo endpoint, anche quando si utilizza la tecnica a single-packet, questa è un’indicazione che il ritardo di back-end interferisce con il tuo attacco. Potresti essere in grado di aggirarlo utilizzando Turbo Intruder per inviare alcune richieste di warming della connessione prima di seguire le principali richieste di attacco.

Abuso di limiti del rate o risorsa

Se il riscaldamento della connessione non fa alcuna differenza, ci sono varie soluzioni a questo problema.

Utilizzando Turbo Intruder, è possibile introdurre un breve ritardo sul lato client. Tuttavia, poiché ciò comporta la divisione delle richieste di attacco effettive su più pacchetti TCP, non sarai in grado di utilizzare la tecnica di attacco a pacchetti singolo. Di conseguenza, su bersagli ad alto jitter, è improbabile che l’attacco funzioni in modo affidabile indipendentemente dal ritardo impostato.

Invece, potresti essere in grado di risolvere questo problema abusando di una funzione di sicurezza comune.

I server Web spesso ritardano l’elaborazione delle richieste se vengono inviate troppe e troppo rapidamente. Inviando un gran numero di richieste fittizie per attivare intenzionalmente la velocità o il limite delle risorse, è possibile causare un ritardo sul lato del server. Ciò rende praticabile l’attacco a pacchetto singolo anche quando è richiesta l’esecuzione ritardata.

Condizioni di gara a singolo endpoint

L’invio di richieste parallele con valori diversi a un singolo endpoint può talvolta attivare potenti condizioni di gara.

Prendi in considerazione un meccanismo di reimpostazione della password che memorizza l’ID utente e reimposta il token nella sessione dell’utente.

In questo scenario, l’invio di due richieste di reimpostazione della password parallela dalla stessa sessione, ma con due nomi utente diversi, potrebbe potenzialmente causare la seguente collisione:

Nota lo stato finale quando tutte le operazioni sono complete:

    session['reset-user'] = victim
    session['reset-token'] = 1234

La sessione ora contiene l’ID utente della vittima, ma il token di ripristino valido viene inviato all’attaccante.

Nota

Affinché questo attacco funzioni, le diverse operazioni eseguite da ciascun processo devono verificarsi nel giusto ordine. Probabilmente richiederebbe più tentativi, o un po’ di fortuna, per raggiungere il risultato desiderato.

Le conferme dell’indirizzo e-mail, o qualsiasi operazione basata su e-mail, sono generalmente un buon obiettivo per le condizioni di gara a single-endpoint. Le e -mail vengono spesso inviate in un thread di fondo dopo che il server emette la risposta HTTP al client, rendendo più probabili le condizioni di gara.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-single-endpoint

Meccanismi di bloccaggio basati su sessioni

Alcuni framework tentano di prevenire la corruzione di dati accidentali utilizzando una qualche forma di blocco delle richieste. Ad esempio, il modulo handler di sessione nativo di PHP elabora solo una richiesta per sessione alla volta.

È estremamente importante individuare questo tipo di comportamento in quanto può altrimenti mascherare le vulnerabilità banalmente sfruttabili. Se noti che tutte le tue richieste vengono elaborate in sequenza, prova a inviare ciascuna di esse utilizzando un token di sessione diverso.

Condizioni parziali di race conditions

Molte applicazioni creano oggetti in più passaggi, che possono introdurre uno stato intermedio temporaneo in cui l’oggetto è sfruttabile.

Ad esempio, quando si registra un nuovo utente, un’applicazione può creare l’utente nel database e impostare la chiave API utilizzando due istruzioni SQL separate. Questo lascia una minuscola finestra in cui esiste l’utente, ma la loro chiave API non è inizializzata.

Questo tipo di comportamento apre la strada agli exploit per cui inietta un valore di input che restituisce qualcosa che corrisponde al valore del database non iniziale, come una stringa vuota o null in JSON, e questo viene confrontato come parte di un controllo di sicurezza.

I framework spesso ti consentono di passare in array e altre strutture di dati non stringa utilizzando la sintassi non standard. Ad esempio, in PHP:

param[]=foo è equivalente a param = [’foo’]
param[]=foo&param[]=bar è equivalente a param = [’foo’, ’bar’]
param[] è equivalente a param = []

Ruby on Rails ti consente di fare qualcosa di simile fornendo una query o un parametro POST con una chiave ma nessun valore. In altre parole, param[key] determina il seguente oggetto lato server:

 params = {"param"=>{"key"=>nil}}

Nell’esempio sopra, ciò significa che durante la finestra della gara, è possibile effettuare potenzialmente richieste API autenticate come segue:

GET /api/user/info?user=victim&api-key[]= HTTP/2
Host: vulnerable-website.com

Nota

È possibile causare collisioni di costruzione parziali simili con una password anziché una chiave API. Tuttavia, man mano che delle password vengono calcolati gli hash, ciò significa che è necessario iniettare un valore che fa corrispondere il digest hash al valore non inizializzato.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-partial-construction

Attacchi sensibili al tempo

A volte potresti non trovare condizioni di gara, ma le tecniche per la fornitura di richieste con tempi precisi possono ancora rivelare la presenza di altre vulnerabilità.

Uno di questi esempi è quando vengono utilizzati i timestamp ad alta risoluzione invece di stringhe casuali crittograficamente sicure per generare token di sicurezza.

Prendi in considerazione un token di ripristino della password che viene solo randomizzato utilizzando un timestamp. In questo caso, potrebbe essere possibile attivare due reimpostazioni password per due utenti diversi, che utilizzano entrambi lo stesso token. Tutto quello che devi fare è il tempo delle richieste in modo che generino lo stesso timestamp.

Lab-https://portswigger.net/web-security/race-conditions/lab-race-conditions-exploiting-time-sensitive-vulnerabilities

Come prevenire le vulnerabilità delle condizioni di gara

Quando una singola richiesta può passare da un’applicazione attraverso sottostati invisibili, comprendere e prevedere il suo comportamento è estremamente difficile. Questo rende impraticabile la difesa. Per garantire correttamente un’applicazione, si consiglia di eliminare i sotto-stati da tutti gli endpoint sensibili applicando le seguenti strategie:

  • evitare di miscelare i dati da diversi luoghi di archiviazione;
  • assicurarsi che gli endpoint sensibili apportino le modifiche allo stato atomico utilizzando le funzionalità di concorrenza del datastore. Ad esempio, utilizzare una singola transazione di database per verificare che il pagamento corrisponda al valore del carrello e confermare l’ordine;
  • come misura di difesa in profondità, sfrutta l’integrità e le caratteristiche di coerenza come i vincoli di unicità della colonna;
  • non tentare di utilizzare un livello di archiviazione dei dati per proteggerne un altro. Ad esempio, le sessioni non sono adatte per prevenire attacchi di sovraccarico limite ai database;
  • assicurati che il framework di gestione della sessione mantenga le sessioni internamente coerenti. L’aggiornamento delle variabili di sessione individualmente anziché in un lotto potrebbe essere un’ottimizzazione allettante, ma è estremamente pericolosa. Questo vale anche per gli Object-relational mapping (ORM); nascondendo concetti come le transazioni, si assumono la piena responsabilità per loro;
  • in alcune architetture, potrebbe essere opportuno evitare completamente lo stato sul lato server. Invece, è possibile utilizzare la crittografia per spingere il lato client di stato, ad esempio, usando JWT. Si noti che questo ha i suoi rischi, come è stato trattato ampiamente in altre sezioni sugli attacchi JWT.

File upload vulnerabilities

PortSwigger Academy – File upload vulnerabilities

Continua il percorso di apprendimento suggerito da “PortSwigger Academy”.

In questa sezione, imparerai come le funzioni di caricamento dei file semplici possono essere utilizzate come potente vettore per un numero di attacchi ad alta severità. Ti mostreremo come bypassare i meccanismi di difesa comuni per caricare una shell Web, consentendo di prendere il pieno controllo di un server Web vulnerabile. Dato quanto sono comuni le funzioni di caricamento dei file, sapere come testarle correttamente è una conoscenza essenziale.

Cosa sono le vulnerabilità del caricamento dei file?

Le vulnerabilità di caricamento dei file sono quando un server Web consente agli utenti di caricare file sul proprio filesystem senza convalidare sufficientemente cose come il loro nome, tipo, contenuto o dimensione. Non riuscire a far rispettare correttamente le restrizioni su questi potrebbe significare che anche una funzione di caricamento di base di immagini può essere utilizzata per caricare file arbitrari e potenzialmente pericolosi. Ciò potrebbe anche includere file di script lato server che abilitano l’esecuzione del codice remoto.

In alcuni casi, l’atto di caricare il file è di per sé abbastanza da causare danni. Altri attacchi possono comportare una richiesta HTTP di follow-up per il file, in genere per attivare la sua esecuzione da parte del server.

Qual è l’impatto delle vulnerabilità del caricamento dei file?

L’impatto delle vulnerabilità del caricamento dei file dipende generalmente da due fattori chiave:

  • Quale aspetto del file il sito Web non convalida correttamente, che si tratti di dimensioni, tipo, contenuto e così via.
  • Quali restrizioni sono imposte sul file una volta che è stato caricato correttamente.

Nel peggiore dei casi, il tipo del file non è validato correttamente e la configurazione del server consente di eseguire alcuni tipi di file (come .php e .jsp) come codice. In questo caso, un utente malintenzionato potrebbe potenzialmente caricare un file di codice lato server che funziona come una shell Web, concedendo loro efficacemente il pieno controllo sul server.

Se il nome file non è validato correttamente, ciò potrebbe consentire a un utente malintenzionato di sovrascrivere i file critici semplicemente caricando un file con lo stesso nome. Se anche il server è vulnerabile al directory traversal, ciò potrebbe significare che gli aggressori sono persino in grado di caricare file in posizioni impreviste.

Non riuscire a assicurarsi che la dimensione del file rientri all’interno delle soglie previste potrebbe anche consentire una forma di attacco di denial-of-service (DOS), per cui l’attaccante riempie lo spazio del disco disponibile.

Come sorgono le vulnerabilità del caricamento dei file?

Dati i pericoli abbastanza ovvi, è raro che i siti Web in natura non abbiano alcuna restrizione su quali file gli utenti possono caricare. Più comunemente, gli sviluppatori implementano ciò che ritengono essere una convalida robusta che è intrinsecamente imperfetta o può essere facilmente bypassata.

Ad esempio, possono tentare una blacklist per tipi di file pericolosi, ma non riescono a tenere conto delle discrepanze di analisi durante il controllo delle estensioni del file. Come con qualsiasi lista nera, è anche facile omettere accidentalmente tipi di file più oscuri che possono essere ancora pericolosi.

In altri casi, il sito Web può tentare di verificare il tipo di file verificando le proprietà che possono essere facilmente manipolate da un utente malintenzionato utilizzando strumenti come il Proxy o il Repeater di Burp.

In definitiva, anche robuste misure di convalida possono essere applicate in modo incoerente attraverso la rete di host e directory che formano il sito Web, con conseguenti discrepanze che possono essere sfruttate.

In che modo i server Web gestiscono le richieste per i file statici?

Prima di esaminare come sfruttare i file caricare le vulnerabilità, è importante avere una comprensione di base di come i server gestiscono le richieste per i file statici.

Storicamente, i siti Web consistevano quasi interamente da file statici che sarebbero stati serviti agli utenti quando richiesti. Di conseguenza, il percorso di ciascuna richiesta potrebbe essere mappato 1: 1 con la gerarchia di directory e file sul filesystem del server. Al giorno d’oggi, i siti Web sono sempre più dinamici e il percorso di una richiesta spesso non ha alcuna relazione diretta al filesystem. Tuttavia, i server Web affrontano ancora le richieste per alcuni file statici, inclusi fogli di stile, immagini e così via.

Il processo per la gestione di questi file statici è ancora in gran parte lo stesso. Ad un certo punto, il server analizza il percorso nella richiesta di identificare l’estensione del file. Quindi lo utilizza per determinare il tipo di file richiesto, in genere confrontandolo con un elenco di mappature preconfigurate tra estensioni e tipi di mime. Cosa succede dopo dipende dal tipo di file e dalla configurazione del server.

  • Se questo tipo di file non è eseguibile, come un’immagine o una pagina HTML statica, il server può semplicemente inviare il contenuto del file al client in una risposta HTTP.
  • Se il tipo di file è eseguibile, come un file PHP e il server è configurato per eseguire file di questo tipo, assegnerà variabili in base alle intestazioni e ai parametri nella richiesta HTTP prima di eseguire lo script. L’output risultante può quindi essere inviato al client in una risposta HTTP.
  • Se il tipo di file è eseguibile, ma il server non è configurato per eseguire file di questo tipo, generalmente risponderà con un errore. Tuttavia, in alcuni casi, il contenuto del file può ancora essere servito al client come testo semplice. Tali errate configurazioni possono occasionalmente essere sfruttate per carpire codice sorgente e altre informazioni sensibili. 

L’intestazione di risposta del Content-Type può fornire indizi sul tipo di file che il server pensa di aver servito. Se questa intestazione non è stata esplicitamente impostata dal codice dell’applicazione, normalmente contiene il risultato della mappatura del tipo di extension/MIME.

Exploiting dell’upload di file senza restrizioni per distribuire Web shell 

Dal punto di vista della sicurezza, lo scenario peggiore è quando un sito Web consente di caricare script sul lato server, come file PHP, Java o Python ed è anche configurato per eseguirli come codice. Questo rende banale creare la tua shell web sul server.

Nota

Una Web shell è uno script dannoso che consente a un utente malintenzionato di eseguire comandi arbitrari su un server Web remoto semplicemente inviando richieste HTTP all’endpoint giusto.

Se sei in grado di caricare correttamente una shell Web, hai effettivamente il pieno controllo sul server. Ciò significa che è possibile leggere e scrivere file arbitrari, exfiltraere dati sensibili, persino utilizzare il server per aprire gli attacchi contro l’infrastruttura interna e altri server al di fuori della rete. Ad esempio, il seguente PHP One-liner potrebbe essere utilizzato per leggere file arbitrari dal filesystem del server:

<?php echo file_get_contents(’/path/to/target/file’); ?>

Una volta caricato, l’invio di una richiesta per questo file dannoso restituirà il contenuto del file di destinazione nella risposta.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-web-shell-upload

Una shell web più versatile può assomigliare a questa:

<?php echo system($_GET[’command’]); ?>

Questo script consente di passare un comando di sistema arbitrario tramite un parametro di query come segue:

GET /example/exploit.php?command=id HTTP/1.1

Exploiting della convalida imperfetta nell’upload di file

In natura, è improbabile che troverai un sito Web che non ha protezione contro gli attacchi sull’upload dei file come abbiamo visto nel laboratorio precedente. Ma solo perché le difese sono in atto, ciò non significa che siano robuste. A volte puoi ancora sfruttare i difetti in questi meccanismi per ottenere una shell Web per l’esecuzione del codice remoto.

Imperfetta convalida del tipo di file 

Quando si invia i moduli HTML, il browser invia in genere i dati forniti in una richiesta POST con il content type application/x-www-form-url-encoded. Va bene per inviare un testo semplice come il tuo nome o indirizzo. Tuttavia, non è adatto per l’invio di grandi quantità di dati binari, come un intero file di immagine o un documento PDF. In questo caso, è preferito il content type  multipart/form-data.

Prendi in considerazione un modulo contenente campi per caricare un’immagine, fornirne una descrizione e inserire il tuo nome utente. L’invio di tale modulo potrebbe comportare una richiesta che assomiglia a questa:

POST /images HTTP/1.1
    Host: normal-website.com
    Content-Length: 12345
    Content-Type: multipart/form-data; boundary=---------------------------012345678901234567890123456

    ---------------------------012345678901234567890123456
    Content-Disposition: form-data; name="image"; filename="example.jpg"
    Content-Type: image/jpeg

    [...binary content of example.jpg...]

    ---------------------------012345678901234567890123456
    Content-Disposition: form-data; name="description"

    This is an interesting description of my image.

    ---------------------------012345678901234567890123456
    Content-Disposition: form-data; name="username"

    wiener
    ---------------------------012345678901234567890123456--

Come puoi vedere, il corpo del messaggio viene diviso in parti separate per ciascuno degli input del modulo. Ogni parte contiene un’intestazione Content-Disposition, che fornisce alcune informazioni di base sul campo di input a cui si riferisce. Queste singole parti possono anche contenere la propria intestazione Content-Type, che indica al server il tipo MIME dei dati inviati utilizzando questo input.

Un modo in cui i siti Web possono tentare di convalidare i caricamenti di file è verificare che questa intestazione Content-Type specifica per input corrisponda a un tipo MIME previsto. Se il server si aspetta solo file di immagini, ad esempio, può consentire solo tipi come image/jpeg e image/png. I problemi possono sorgere quando il valore di questa intestazione è implicitamente affidabile dal server. Se non viene eseguita alcuna ulteriore convalida per verificare se il contenuto del file corrisponde effettivamente al presunto tipo di MIME, questa difesa può essere facilmente bypassata usando strumenti come Burp Repeater.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-content-type-restriction-bypass

Mentre in primo luogo è chiaramente meglio impedire che i tipi di file pericolosi vengano caricati, la seconda linea di difesa è impedire al server di eseguire qualsiasi script che “scivolano” attraverso la rete.

Per precauzione, i server eseguono generalmente solo script il cui tipo di MIME è stato esplicitamente configurato per essere eseguito. Altrimenti, possono restituire una sorta di messaggio di errore o, in alcuni casi, servire invece il contenuto del file come testo semplice:

GET /static/exploit.php?command=id HTTP/1.1
    Host: normal-website.com


    HTTP/1.1 200 OK
    Content-Type: text/plain
    Content-Length: 39

    <?php echo system($_GET['command']); ?>

Questo comportamento è potenzialmente interessante a sé stante, in quanto può fornire un modo per carpire il codice sorgente, ma annulla qualsiasi tentativo di creare una shell Web.

Questo tipo di configurazione spesso differisce tra le directory. Una directory a cui vengono caricati i file forniti dall’utente avrà probabilmente controlli molto più severi rispetto ad altre posizioni sul filesystem che si presume siano fuori portata per gli utenti finali. Se riesci a trovare un modo per caricare uno script in una directory diversa che non dovrebbe contenere file forniti dall’utente, il server potrebbe eseguire lo script.

Nota

I server Web utilizzano spesso il campo filename nelle richieste multipart/form-data per determinare il nome e la posizione in cui il file deve essere salvato.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-path-traversal

Dovresti inoltre notare che anche se puoi inviare tutte le tue richieste allo stesso nome di dominio, questo spesso indica un server proxy inverso di qualche tipo, come un bilanciamento del carico. Le tue richieste saranno spesso gestite da ulteriori server dietro le quinte, che possono anche essere configurati in modo diverso.

Lista nera di file pericolosi insufficiente 

Uno dei modi più ovvi per impedire agli utenti di caricare script dannosi è quello di blacklist estensioni di file potenzialmente pericolose come .php. La pratica della lista nera è intrinsecamente imperfetta in quanto è difficile bloccare esplicitamente ogni possibile estensione di file che potrebbe essere utilizzata per eseguire il codice. Tali liste nere a volte possono essere bypassate utilizzando estensioni di file alternative meno note che possono essere ancora eseguibili, come .php5, .shtml e così via.

Overriding della configurazione del server

Come abbiamo discusso nella sezione precedente, i server in genere non eseguiranno file a meno che non siano stati configurati per farlo. Ad esempio, prima che un server Apache eseguirà i file PHP richiesti da un client, gli sviluppatori potrebbero dover aggiungere le seguenti direttive al loro file /etc/apache2/apache2.conf:

LoadModule php_module /usr/lib/apache2/modules/libphp.so
    AddType application/x-httpd-php .php

Molti server consentono inoltre agli sviluppatori di creare file di configurazione speciali all’interno delle singole directory per sovrascrivere o aggiungere a una o più impostazioni globali. I server Apache, ad esempio, caricheranno una configurazione specifica della directory da un file chiamato .htaccess se uno è presente.

Allo stesso modo, gli sviluppatori possono effettuare una configurazione specifica per la directory sui server IIS utilizzando un file web.config. Ciò potrebbe includere direttive come le seguenti, che in questo caso consente di servire i file JSON agli utenti:

<staticContent>
    <mimeMap fileExtension=".json" mimeType="application/json" />
    </staticContent>

I server Web utilizzano questi tipi di file di configurazione quando presenti, ma normalmente non è consentito accedervi utilizzando le richieste HTTP. Tuttavia, occasionalmente potresti trovare server che non ti impediscono di caricare il tuo file di configurazione dannoso. In questo caso, anche se l’estensione del file di cui hai bisogno è nella lista nera, potresti essere in grado di indurre il server a mappare un’estensione arbitraria e personalizzata su un tipo MIME eseguibile.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-extension-blacklist-bypass

Estensioni di file offuscanti

Anche le liste nere più esaustive possono potenzialmente essere bypassate usando le classiche tecniche di offuscamento. Supponiamo che il codice di convalida sia sensibile al caso e non riconosce che exploit.pHp è in realtà un file .php. Se il codice che successivamente mappa l’estensione del file su un tipo MIME non è case sensitive, questa discrepanza consente a file PHP dannosi di sgattaiolare la convalida e alla fine essere eseguito dal server.

Puoi anche ottenere risultati simili utilizzando le seguenti tecniche:

  • fornire più estensioni: a seconda dell’algoritmo utilizzato per analizzare il nome file, il seguente file può essere interpretato come un file PHP o un’immagine JPG: exploit.php.jpg;
  • aggiungere caratteri finali: alcuni componenti spoglieranno o ignoreranno spazi bianchi, punti e simili: exploit.php;
  • provare a utilizzare la codifica URL (o la codifica a doppio URL) per punti, slash e back slash. Se il valore non viene decodificato durante la convalida dell’estensione del file, ma viene successivamente decodificato sul lato server, questo può anche consentire di caricare file dannosi che sarebbero altrimenti bloccati: exploit%2Ephp;
  • aggiungere punti e virgole o caratteri byte null codificati con URL prima dell’estensione del file. Se la convalida è scritta in un linguaggio di alto livello come PHP o Java, ma il server elabora il file utilizzando funzioni di livello inferiore in C/C ++, ad esempio, ciò può causare discrepanze in quella che viene trattata come la fine del file: exploit.asp, .jpg o exploit.asp%00.jpg;
  • provare a utilizzare i caratteri unicode multibyte, che possono essere convertiti in byte e punti nulli dopo la conversione o la normalizzazione Unicode. Sequenze come xC0 x2E, xC4 xAE or xC0 xAE possono essere tradotte in x2E se il filename è analizzato come stringa UTF-8, ma poi convertito in caratteri ASCII prima di essere utilizzato in un percorso.

Altre difese prevedono lo stripping o la sostituzione di estensioni pericolose per impedire l’esecuzione del file. Se questa trasformazione non viene applicata in modo ricorsivo, è possibile posizionare la stringa proibita in modo tale da rimuoverla lasciando ancora una valida estensione del file. Ad esempio, considera cosa succede se si spoglia .php dal seguente nome file:

exploit.p.phphp

Questa è solo una piccola selezione dei molti modi in cui è possibile offuscare le estensioni dei file.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-obfuscated-file-extension

Convalida imperfetta del contenuto del file

Invece di fidarsi implicitamente del Content-Type specificato in una richiesta, i server più sicuri cercano di verificare che il contenuto del file corrisponda effettivamente a ciò che è previsto.

Nel caso di una funzione di caricamento di una immagine, il server potrebbe provare a verificare alcune proprietà intrinseche di un’immagine, come le sue dimensioni. Se provi a caricare uno script PHP, ad esempio, non avrà alcuna dimensione. Pertanto, il server può dedurre che non può essere un’immagine e rifiutare il caricamento di conseguenza.

Allo stesso modo, alcuni tipi di file possono sempre contenere una sequenza specifica di byte nell’header o nel footer di pagina. Questi possono essere usati come un’impronta digitale o una firma per determinare se i contenuti corrispondono al tipo previsto. Ad esempio, i file JPEG iniziano sempre con i byte FF D8 FF.

Questo è un modo molto più robusto di convalidare il tipo di file, ma anche questo non è infallibile. Utilizzando strumenti speciali, come Exiftool, può essere banale creare un file JPEG polyglot contenente codice dannoso all’interno dei suoi metadati.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-polyglot-web-shell-upload

Sfruttamento delle condizioni di caricamento dei file

I framework moderni sono più resistenti contro questo tipo di attacchi. In genere non caricano file direttamente nella destinazione prevista sul filesystem. Invece, prendono precauzioni come il caricamento in una directory temporanea e sandboxing e randomizzando il nome per evitare di sovrascrivere i file esistenti. Quindi eseguono la convalida su questo file temporaneo e lo trasferiscono a destinazione solo una volta considerato sicuro.

Detto questo, gli sviluppatori a volte implementano il proprio processo di caricamento di file indipendentemente da qualsiasi framework. Non solo è abbastanza complesso da implementare bene, ma può anche introdurre condizioni pericolose che consentono a un aggressore di aggirare completamente anche la convalida più robusta.

Ad esempio, alcuni siti Web caricano il file direttamente sul filesystem principale e quindi lo rimuovono se non supera la convalida. Questo tipo di comportamento è tipico nei siti Web che si basano sul software antivirus e di verifica del malware. Ciò può richiedere solo pochi millisecondi, ma per il breve periodo esiste sul server, l’attaccante può potenzialmente ancora eseguirlo.

Queste vulnerabilità sono spesso estremamente subdole, il che le rende difficili da rilevare durante i test blackbox, a meno che non si riesca a trovare un modo per far trapelare il codice sorgente pertinente.

Lab-https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-race-condition

Race condition nei carichi di file basati su URL

Condizioni di corsa simili possono verificarsi in funzioni che consentono di caricare un file fornendo un URL. In questo caso, il server deve recuperare il file su Internet e creare una copia locale prima di poter eseguire qualsiasi convalida.

Poiché il file viene caricato utilizzando HTTP, gli sviluppatori non sono in grado di utilizzare i meccanismi integrati del loro framework per la convalida in modo sicuro. Invece, possono creare manualmente i propri processi per archiviare e convalidare temporaneamente il file, che potrebbe non essere così sicuro.

Ad esempio, se il file viene caricato in una directory temporanea con un nome randomizzato, in teoria, dovrebbe essere impossibile per un attaccante sfruttare eventuali condizioni di gara. Se non conoscono il nome della directory, non saranno in grado di richiedere il file per attivare la sua esecuzione. D’altra parte, se il nome della directory randomizzato viene generato utilizzando funzioni pseudo-casuali come uniqid() di PHP, può potenzialmente essere rafforzato.

Per semplificare gli attacchi come questo, puoi provare a prolungare il tempo impiegato per elaborare il file, allungando così la finestra per il brute-forcing del nome della directory. Un modo per farlo è caricare un file più grande. Se viene elaborato in blocchi, puoi potenzialmente trarne vantaggio creando un file dannoso con il carico utile all’inizio, seguito da un gran numero di byte arbitrari di imbottitura.

Sfruttare le vulnerabilità del caricamento dei file senza esecuzione del codice remoto

Negli esempi che abbiamo esaminato finora, siamo stati in grado di caricare script sul lato server per l’esecuzione del codice remoto. Questa è la conseguenza più grave di una funzione di upload di file insicuri, ma queste vulnerabilità possono ancora essere sfruttate in altri modi.

Caricamento di script dannoso lato client 

Sebbene potresti non essere in grado di eseguire script sul server, potresti comunque essere in grado di caricare script per gli attacchi sul lato client. Ad esempio, se è possibile caricare file HTML o immagini SVG, è possibile utilizzare potenzialmente i tag <script> per creare payload XSS archiviati.

Se il file caricato viene quindi visualizzato in una pagina visitata da altri utenti, il loro browser eseguirà lo script quando tenta di visualizzare la pagina. Si noti che a causa delle restrizioni delle politiche della stessa origine, questi tipi di attacchi funzionano solo se il file caricato viene servito dalla stessa origine a cui lo si carica.

Sfruttando le vulnerabilità nell’analisi di file caricati

Se il file caricato sembra essere archiviato e servito in modo sicuro, l’ultima risorsa è provare a sfruttare le vulnerabilità specifiche per l’analisi o l’elaborazione di diversi formati di file. Ad esempio, sai che il server analizza i file basati su XML, come i file Microsoft Office .doc o .xls, questo potrebbe essere un potenziale vettore per gli attacchi di iniezione XXE.

Caricamento di file utilizzando PUT

Vale la pena notare che alcuni server Web potrebbero essere configurati per supportare le richieste PUT. Se non sono in atto difese appropriate, ciò può fornire un mezzo alternativo per caricare file dannosi, anche quando una funzione di caricamento non è disponibile tramite l’interfaccia Web.

PUT /images/exploit.php HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-httpd-php
Content-Length: 49

<?php echo file_get_contents('/path/to/file'); ?>

Nota

È possibile provare a inviare richieste OPTIONS a endpoint diversi per testare qualsiasi supporto per pubblicizzare per il metodo PUT.

Come prevenire le vulnerabilità del caricamento dei file

Consentire agli utenti di caricare file è all’ordine del giorno e non deve essere pericoloso fintanto che prendi le precauzioni giuste. In generale, il modo più efficace per proteggere i propri siti Web da queste vulnerabilità è implementare tutte le seguenti pratiche:

  • controlla l’estensione del file con una whitelist di estensioni consentite piuttosto che una lista nera di quelle proibite. È molto più facile indovinare quali estensioni potresti voler consentire che indovinare quali un attaccante potrebbe provare a caricare;
  • assicurarsi che il filename non contenga alcun substrings che può essere interpretato come una directory o una sequenza di attraversamento (../);
  • rinomina i file caricati per evitare collisioni che possono causare la sovrascrittura di file esistenti;
  • non caricare file sul filesystem permanente del server fino a quando non sono stati completamente validati;
  • per quanto possibile, utilizzare un framework consolidato per la preelaborazione degli upload di file anziché tentare di scrivere i propri meccanismi di convalida.