Nel precedente articolo abbiamo brevemente analizzato le possibili minacce di sicurezza in iOS. Restando sulla stessa onda, parleremo oggi di uno dei metodi più semplici che permette di trovare vulnerabilità di sicurezza nelle app iOS: il fuzzing.
Introduzione
Nel precedente articolo abbiamo visto come funziona la superficie di attacco di iOS, specificando che un utente malintenzionato ha potenzialmente molti modi per prelevare dati da un dispositivo, sia lato server attraverso lo stack wireless, Bluetooth o SMS che da quello client, nel quale sono presenti molte app tra cui Safari, Mail, App Store e così via..
La chiave è trovare un input per uno di questi programmi che possa essere usato per modificare il comportamento dell’applicazione a proprio piacimento. È qui che entra in gioco il fuzzing, metodo estremamente semplice che permette di testare dinamicamente le app inviando ripetutamente dati volutamente “distorti” per capire se causano un problema ad una particolare app. Tutto questo vi ricorda qualcosa? Proprio come state pensando il bug del carattere indiano è stato probabilmente scoperto attraverso una sorta di fuzzing e fortunatamente è stato risolto con un fix.
La cosa sorprendente è che il fuzzing consente di scoprire vulnerabilità in iOS a volte con uno sforzo minimo. In altre parole, è il modo più semplice per trovare bug in iOS.
Come funziona il fuzzing
Noto anche come analisi dinamica, il fuzzing si basa sull’invio di un input “particolare” dato in pasto ad una certa app, nella speranza che mostri qualche problema di sicurezza. Potreste pensare che si tratti di un metodo stupido, ma più che altro lo definirei semplice, non stupido. In passato infatti è stato usato per trovare numerosi bug relativi alla sicurezza in prodotti diversi come Apache HTTP Server, l’interfaccia Microsoft RPC e naturalmente anche MobileSafari su iOS.
L’idea alla base del metodo è quella di inviare ripetutamente un input “distorto”: un’app ben progettata (e ben implementata!) dovrebbe essere in grado di gestire qualsiasi input fornito in ingresso, rifiutando quelli non validi. Quando invece riceve input validi, deve eseguire tutte le operazioni previste.
In nessun caso il programma dovrebbe bloccarsi o smettere di funzionare: il fuzzing cerca di corrompere questo meccanismo inviando milioni di input all’app per capire se si arresta (o esegue altre azioni inaccettabili). Monitorando l’app durante il fuzzing, il tester può quindi determinare quali ingressi hanno causato “guasti” nell’applicazione.
I tipici di bug rilevati con il fuzzing includono maggiormente vulnerabilità di corruzione della memoria, come buffer overflow. Supponiamo ad esempio che un programmatore presuma che in un certo campo della sua app è necessario inserire un numero di telefono; supponiamo inoltre che non si debbano superare i 32 byte e quindi prepari un buffer di tale dimensione per i dati. Se lo sviluppatore non controlla esplicitamente i dati, potrebbe verificarsi un problema perché i dati “al di fuori del buffer” previsto potrebbero essere danneggiati, se l’input supera i 32 byte. Per questo motivo, il fuzzing viene spesso considerato come una tecnica che verifica le ipotesi dello sviluppatore inviando dati non validi, dati non gestibili e dati con dimensioni spropositate.
Uno dei vantaggi di questa tecnica è che è molto semplice impostare un ambiente di test fuzzing di base, senza necessariamente conoscere il programma che si sta testando. Nel caso più semplice, tutto ciò di cui si ha bisogno è un app e un input, unito ovviamente al tempo necessario per scovare il bug!
Non è oro tutto ciò che luccica, infatti se da un lato queste tecnica ha molti vantaggi, dall’altro presenta alcuni inconvenienti tecnici. Alcuni bug ad esempio, non possono essere trovati con il fuzzing perchè richiedono che siano soddisfatte condizioni molto precise, ed è improbabile che il fuzzing trovi bug “complessi”, almeno in un ragionevole periodo di tempo. Per questi motivi, non è sempre la scelta migliore.
Esempio
Il modo in cui il fuzzing funziona è semplice: prendiamo un input valido per un’app. Potrebbe trattarsi di un file, come un file .mov, o alcuni input di rete, come una semplice sessione HTTP o anche solo un insieme di argomenti di riga di comando. Iniziamo a modificare casualmente questo input, ad esempio:
/index.html HTTP / 1.0
potremmo modificarlo in vari modi:
GEEEEEEEEEEEEEET /index.html HTTP / 1.0
GET / / / / / / / / / / / / / / / //index.html HTTP / 1.0
GET /index .........................html HTTP / 1.0
GET /index.htmllllllllllllllllllllllllllllllllllllllll HTTP / 1.0 GET /index.html HTTP / 1.00000000000000000
Se il programmatore ha fatto supposizioni errate sulla dimensione di uno di questi campi, questi input potrebbero innescare un qualche tipo di errore. La cosa bella è che per fare queste modifiche casuali, non bisogna conoscere assolutamente nulla sul modo in cui funziona il protocollo HTTP! Tuttavia, com’è facile immaginare, la maggior parte dei server Web che eseguono un qualsiasi controllo di integrità sui dati rifiuteranno rapidamente la maggior parte di questi input: è vero che bisogna apportare modifiche agli input validi per trovare i bug, ma se si apportano modifiche troppo drastiche, gli input verranno respinti in un attimo. Bisogna trovare il punto debole, apportando abbastanza modifiche per causare problemi ma non abbastanza per rendere i dati esageratamente non validi.
Anche per questo articolo è tutto. Per eventuali domande, curiosità o feedback potete lasciare un commento qui in basso, a presto!