
Divulgazione delle vulnerabilità delle telecamere Accfly: CVE-2020-25782, CVE-2020-25783, CVE-2020-25784, CVE-2020-25785.
All'inizio del 2020, presso il mio precedente posto di lavoro, ho avuto la possibilità di prendere parte a un evento in stile pwn2own interno. C'erano diversi target disponibili, ma ero maggiormente interessato alla Accfly Wireless Security camera. Sfortunatamente non ero riuscito a completare la mia ricerca per l'evento vero e proprio, ma dato che non c'erano altri tentativi su questo dispositivo, ho continuato.
L'obiettivo principale della ricerca erano le vulnerabilità che potessero portare all'esecuzione remota di codice (RCE). Questo tipo di vulnerabilità consente a un attaccante di assumere il pieno controllo del dispositivo e, nel caso di una videocamera, può risultare in un completo compromesso della privacy del proprietario. Purtroppo il firmware del dispositivo è risultato essere pieno di tali problemi.
Prima di tutto, il dispositivo non fornisce alcuna autenticazione. Di conseguenza, un attaccante in grado di connettersi ad esso può accedervi e riconfigurarlo liberamente. Nella forma più semplice, è possibile riavviare continuamente il dispositivo, rendendolo completamente inutilizzabile per l'utente legittimo. La portata di questo attacco è leggermente limitata, poiché il dispositivo è progettato per essere utilizzato all'interno di una rete WiFi, di solito dietro NAT, quindi non direttamente raggiungibile da Internet. Tuttavia, la mancanza di crittografia tra il dispositivo e l'app dello smartphone del proprietario, insieme all'uso del server del fornitore come proxy per la comunicazione, crea un'opportunità per attacchi MitM o di manipolazione DNS, che possono superare la restrizione NAT del WiFi.
Inoltre, l'applicazione utilizza un protocollo binario proprietario per la comunicazione. È stata implementata in un mix di C e C++ ed è risultata piena di funzioni di gestione delle stringhe non sicure. L'eseguibile principale contiene una grande quantità di codice inutilizzato, il che suggerisce che venga riutilizzato su altri dispositivi. Questo rende la manutenzione più difficile e aumenta la superficie d'attacco. L'applicazione non abilita alcun meccanismo di sicurezza moderno, che la proteggerebbe da molte comuni tecniche di sfruttamento. Inoltre, non limita nemmeno i permessi utente, eseguendo come root - con i più alti privilegi disponibili.
Come risultato di questa ricerca, sono state documentate le seguenti quattro vulnerabilità.
CNetClientManage::ServerIP_Proto_Set durante la gestione dei messaggi in ingressoCNetClientTalk::OprMsg durante la gestione dei messaggi in ingressoCNetClientGuard::SubOprMsg durante la gestione dei messaggi in ingressoCFtpProtocol::FtpLogin durante la procedura di aggiornamentoPer tre di esse sono stati sviluppati exploit RCE, che consentono a un attaccante di ottenere il controllo completo del dispositivo. Tuttavia, a causa della mancanza di risposta da parte del fornitore ai tentativi di segnalazione delle vulnerabilità, questo repository contiene solo PoC limitati, che si limitano a far crashare l'applicazione.
I problemi sono stati trovati nella versione del software V3.10.73 e verificati nella versione V4.15.77, l'ultima disponibile al momento della pubblicazione (26 gennaio 2021).
In caso di domande, non esitare a contattarmi via email (vedi commit git) o tramite le issue di Github. Se hai un dispositivo IoT che pensi possa essere interessante da hackerare, stai cercando un Security Researcher o vuoi solo salutare, sono felice di sentirti. Puoi anche offrirmi un caffè!
Il dispositivo preso di mira è una videocamera, controllata tramite l'app mobile associata. La mia analisi è iniziata dal traffico di rete della videocamera ed è proseguita con il firmware della stessa. Per tutte le comunicazioni viene utilizzato un protocollo binario personalizzato. I comandi vengono inviati direttamente al dispositivo mobile quando si è nella stessa rete oppure passano attraverso il server del produttore del dispositivo. Il software della videocamera ascolta su più porte TCP (23456, 34567) e UDP (34568, 34569). Non c'è crittografia né autenticazione per il traffico di rete, il che consente attacchi MitM o accesso diretto quando la videocamera è esposta sulla rete. Sembra probabile che anche l'accesso al flusso video sia possibile senza autenticazione, ma non ho fatto reverse engineering abbastanza del protocollo proprietario per provarlo.
Dopo una breve panoramica della comunicazione, il passo successivo è stato cercare di ottenere l'accesso al firmware del dispositivo. Il mio primo tentativo è stato scaricarlo direttamente dirottando il processo di aggiornamento del dispositivo, ma non è successo nulla di simile nel traffico di rete. Sarei rimasto bloccato in questa fase, se non fosse stato per l'aiuto davvero necessario di un collega che ha estratto il firmware dalla memoria flash, permettendomi di continuare questa ricerca.
Il firmware è risultato eseguire Linux su CPU MIPS little-endian. C'è esattamente un processo interessante, chiamato Alloca, che è responsabile dell'acquisizione video e gestisce anche tutte le comunicazioni di rete. L'applicazione è creata in C++ e contiene molto codice che non viene utilizzato su questo dispositivo. Ciò indica che lo stesso software viene utilizzato anche su dispositivi diversi.
Anche se questo problema è stato trovato per ultimo, è cruciale per lo sfruttamento effettivo della maggior parte degli altri, perché derivano dall'uso di funzioni C non sicure per la gestione delle stringhe. Sebbene esistano diverse tecniche che possono essere utilizzate per eseguire codice con successo in scenari simili, l'applicazione è creata in modo tale che siano per lo più inutili. Il problema principale è che il codice e i dati di Alloca sono allocati staticamente a indirizzi bassi ( < 0x01000000). Quindi i tentativi di riutilizzare il codice esistente (cioè ROP e simili) non sono utili, poiché richiedono la capacità di scrivere indirizzi nella memoria del programma. Poiché le stringhe C usano \x00 come carattere di terminazione e le funzioni di stringa terminano l'elaborazione al primo byte di questo tipo, non è possibile usare più di un singolo byte NULL. Inoltre, la posizione dello stack è randomizzata e l'applicazione è pesantemente multi-thread, il che rende altre tecniche molto meno affidabili.
Questa vulnerabilità è il risultato della condivisione di dati tra più thread e dell'uso non sicuro di strcpy. Anche se ho analizzato questo particolare problema per molto tempo, non avevo individuato la possibilità di usarlo come vettore di fuga di dati fino a poche settimane prima di questa pubblicazione. Curiosamente, grazie alla fuga dell'indirizzo heap di un oggetto C++, questa vulnerabilità consente anche l'esecuzione remota di codice. Tuttavia, questo attacco non è incluso in questo rapporto.
L'applicazione Alloca può aggiornarsi via FTP. Questa operazione può essere richiesta da un server, che fornisce anche nome utente, password e nome file necessari. La funzione che avvia l'aggiornamento è mostrata qui:

Tre chiamate a strcpy sono chiaramente non sicure e portano a un overflow dell'heap, poiché l'oggetto ftpUpgrade è allocato dinamicamente. Sfortunatamente, l'ordine in cui vengono eseguite le copie e il layout della struttura ftpUpgrade rendono impossibile avviare effettivamente un thread che perda dati. Osservando più da vicino il pacchetto in arrivo, si rivela la seguente struttura:
struct ftp_upgrade_pkt {
struct pktHeader;
char username[16];
char password[16];
char filename[128];
}
mentre l'oggetto ftpUpgrade assomiglia a qualcosa del genere:
struct CNetClientFtpUpgrade {
// ... something here
char filename[128];
char unknown[6];
char username[16];
char password[16];
CFtpDownlad *;
CNetClientConnect*;
int something[5];
bool threadRunning;
// ... and more
}
La fuga può verificarsi dopo che uno dei puntatori interni (CFtpDownload*, CNetClientConnect*) viene compilato dall'applicazione. Inoltre, il nome utente e la password vengono copiati (ancora una volta, ma in modo sicuro questa volta) nel nuovo oggetto creato prima che il suo puntatore venga memorizzato nella posizione sfruttabile, quindi la fuga può avvenire solo con filename. Di conseguenza, il nome file deve essere molto lungo, ma a causa dell'ordine e del comportamento di terminazione di strcpy, un nome file sufficientemente lungo comporterà un nome utente e una password ancora più lunghi, che di fatto sovrascriveranno threadRunning e non avvieranno affatto un thread.
Se questo codice fosse a thread singolo, non si potrebbe fare molto. Ma poiché il nuovo thread FtpDownload viene generato ed esegue la funzione DownloadFile, presenta un'opportunità interessante in quanto condivide l'oggetto CNetClientFtpUpgrade con il thread che gestisce i pacchetti in arrivo. Non solo ha più operazioni di I/O che possono essere controllate esternamente (richieste DNS, elaborazione della connessione FTP), ma tenta anche di connettersi all'FTP fino a 10 volte (questo avviene nel chiamante di DownloadFile). Ciò consente di controllare l'esecuzione del thread FtpDownload (bloccandolo su operazioni di I/O), dando così tempo al thread di gestione dei messaggi per elaborare altre richieste.

In breve, inviando più richieste di aggiornamento, è possibile modificare il filename (e altri parametri) usato dal thread FtpDownload già in esecuzione e ricevere l'indirizzo heap fatto fuoriuscire. Come bonus, la funzione FtpSize (evidenziata in verde) usa il buffer all'interno dell'oggetto referenziato dall'indirizzo trapelato, per memorizzare il filename stesso, il che consente un'iniezione banale del primo stadio dello shellcode. Le uniche limitazioni qui sono la lunghezza e la mancanza di byte NULL, a causa dell'uso di strcpy. Viene fornito un PoC di esempio che si limita a far fuoriuscire un indirizzo heap dal dispositivo.
overflow del buffer basato su stack non autenticato nella funzione CNetClientManage::ServerIP_Proto_Set
La completa mancanza di autenticazione nella gestione del traffico in arrivo mi ha spinto a cercare i gestori dei pacchetti. Una delle funzioni interessanti è ServerIP_Proto_Set. Sembra essere utilizzata per creare una sovrascrittura statica per la risoluzione DNS. Non ho trovato un modo per reindirizzare il traffico in questo modo, ma c'è un altro buffer overflow qui (evidenziato in arancione).

I dati, letti direttamente dal pacchetto, vengono utilizzati all'interno della funzione sprintf. In questo caso si presume che i dati del pacchetto entrino in un buffer di 16 byte, ma l'uso del semplice formato %s consente di scrivere tutti i byte che si desidera, purché non contengano NULL.
Questa vulnerabilità è piuttosto limitata. Sebbene sia possibile scrivere molti dati sullo stack, quindi l'uso di una NOP-sledge potrebbe funzionare, non è possibile scrivere byte NULL. Anche il tentativo di scrivere un singolo byte NULL fallirà, poiché il formato della funzione sprintf lo fa precedere da un \n. Un altro ostacolo è un oggetto CMutex memorizzato dopo il buffer. Qualsiasi tentativo di overflow deve riempire questo mutex con un valore corretto (o almeno uno che soddisfi il distruttore di CGuard - evidenziato in rosso). Questo è problematico, poiché il distruttore dereferenzia due volte la variabile passata e poi usa il suo valore nella chiamata a pthread_mutex_unlock. Dopo alcuni test ho scoperto che un buffer riempito con NULL è sufficiente per tornare correttamente da pthread_mutex_unlock, ma doveva comunque essere dereferenziato in un indirizzo di memoria appropriato.
La fuga di dati viene in soccorso. L'attacco è un po' complesso, poiché abbiamo bisogno di un indirizzo heap che non contenga byte NULL. Fortunatamente la ricerca è resa più semplice, poiché il dispositivo ci offre la possibilità di eseguire un riavvio remoto non autenticato. Ogni volta viene fornita una diversa allocazione dello spazio degli indirizzi heap. Quindi è possibile semplicemente resettare il dispositivo e far fuoriuscire un indirizzo finché non se ne trova uno appropriato. Comodamente, questo consente anche di memorizzare un breve primo stadio dello shellcode. Poiché dobbiamo superare il problema del mutex (serve un puntatore a un puntatore), facciamo fuoriuscire un altro indirizzo, questa volta passando un indirizzo precedentemente trapelato come nome file. Il diagramma seguente mostra il layout di memoria previsto:

Se tutto va come previsto, è possibile passare un secondo indirizzo come mutex e il primo come indirizzo di ritorno. Tuttavia, questo non è necessario per far semplicemente crashare un'applicazione come fa il PoC.
overflow del buffer basato su heap non autenticato nella funzione CNetClientTalk::OprMsg
Il dispositivo dovrebbe consentire la comunicazione vocale bidirezionale. Un altro handler per i pacchetti in ingresso sembra essere responsabile della ricezione e riproduzione dell'audio. Il pacchetto di rete audio_pkt_hdr è descritto dalla seguente struttura:
struct audio_pkt_hdr {
struct pktHeader field_0x0;
int field_0x14
int field_0x18
int field_0x1c
char audioBuff[0x140];
}
Uno dei campi della struttura pktHeader è la lunghezza del pacchetto (come trasferito sulla rete). Questo campo può essere impostato liberamente dal mittente. La parte vulnerabile è la copia dei dati direttamente dal pacchetto in arrivo utilizzando il valore di lunghezza non attendibile fornito nell'intestazione del pacchetto in arrivo.

Come si può vedere, l'oggetto CNetClientTalk viene creato con il seguente costruttore:

quindi la chiamata a memcpy di cui sopra provoca un overflow del buffer sull'heap. Purtroppo lo sfruttamento effettivo di questo problema è piuttosto difficile. Anche se è possibile sovrascrivere ripetutamente l'heap, non ho trovato un modo per controllare quali dati verranno memorizzati sull'heap dopo il buffer che va in overflow. Poiché l'applicazione ha oltre 50 thread attivi, alcuni dei quali responsabili dell'elaborazione di audio e video, (de)alloca costantemente memoria. Ciò fa sì che i dati dell'heap cambino continuamente, rendendo difficile prevedere cosa c'è dopo il buffer e sovrascriverlo correttamente.
overflow del buffer basato su stack non autenticato nella funzione CNetClientGuard::SubOprMsg
Ecco un altro handler dei pacchetti in ingresso. Questa volta il pacchetto di rete ha la seguente struttura (l'intestazione comune del pacchetto è omessa):
struct pkt_hdr_sub_cliGuard {
dword deviceId;
dword userId;
dword magic;
dword subCmd;
dword field_0x10;
dword field_0x14;
dword field_0x18;
dword field_0x1c;
dword guard_icommand;
dword moreThenRandomStackValue;
dword itemCnt;
char array_of_0x18[24];
};
Ancora una volta, la parte interessante è l'ultimo array (poiché possiamo far crescere questo pacchetto quanto vogliamo), che contiene una struttura interna di dimensione 24. La vulnerabilità deriva dal presupposto che il itemCnt ricevuto non superi 6, perché il buffer di destinazione della copia ha dimensione 144 (=24*6), come visibile nel seguente listato (evidenziato in arancione):

Questa volta la copia viene eseguita usando memcpy (evidenziato in verde), quindi non c'è limite ai caratteri consentiti. La copia viene effettuata a blocchi, tramite un ciclo while (evidenziato in blu). Vale la pena notare che il contatore cnt_v0 diminuisce all'interno del ciclo, quindi i blocchi vengono copiati in ordine inverso. Includendo le variabili che seguono il buffer vulnerabile buf, l'overflow deve avere 256 byte, poi 4 registri ($s0-$s3) e $ra. Poiché non conosciamo il layout della memoria, il codice del PoC usa una tecnica ROP. Viene utilizzato un singolo gadget, che riproduce uno dei suoni integrati del dispositivo (e va in crash).
overflow del buffer basato su stack non autenticato nella funzione CFtpProtocol::FtpLogin
Una delle direzioni iniziali della mia analisi era cercare la procedura di aggiornamento. Come ho scoperto, il dispositivo ha una funzionalità di aggiornamento FTP, che può essere avviata inviando una richiesta di aggiornamento e comporta il download del firmware da un sito FTP esterno. Come per le altre vulnerabilità, non è necessario autenticarsi prima di richiedere l'aggiornamento del dispositivo. Un'analisi approfondita della funzionalità FTP ha scoperto un overflow del buffer basato su stack nella funzione CFtpProtocol::FtpLogin. Come possiamo vedere nel listato decompilato qui sotto, la funzione passa un array char di dimensione 256 alla funzione FtpPwd.

FtpPwd viene utilizzata per ottenere la directory di lavoro corrente dal server FTP. Carica il suo buffer interno con fino a 1500 byte di risposta e poi li copia nel buffer fornito. Questa sequenza di chiamate provoca un overflow di 1242 byte. In questo caso i caratteri consentiti sono molto limitati, poiché l'uso di " (virgolette doppie) ridurrebbe la stringa di input (strchr viene usata per cercare char in una stringa C) e non farebbe overflow del buffer. Fortunatamente è sufficiente fornire un singolo indirizzo, verso cui verrà reindirizzata l'esecuzione del codice.

Per sfruttare questa vulnerabilità è necessario controllare il DNS o reindirizzare (o fare MitM) la connessione al server FTP. L'applicazione non ha alcuna protezione moderna attiva, quindi è possibile eseguire il codice direttamente dallo stack. Senza una fuga di un indirizzo, il meglio che si può fare è indovinare la posizione dello stack o reindirizzare l'esecuzione a una singola funzione, che poi farà crashare l'applicazione. I miei primi tentativi erano proprio questo: riprodurre uno dei suoni integrati, come fornito nel PoC. Usando la fuga di dati, è possibile ottenere il pieno controllo del dispositivo.