Studiare un'apparecchiatura VPN: il percorso di un ricercatore

Akamai Wave Blue

scritto da

Ben Barnea

February 11, 2025

Akamai Wave Blue

scritto da

Ben Barnea

Ben Barnea è Security Researcher in Akamai con interesse e competenze nella conduzione di ricerche sulle vulnerabilità e sulla sicurezza di basso livello in varie architetture, tra cui quelle in Windows e Linux, nonché nell'IoT (Internet of Things) e nei dispositivi mobili. Adora scoprire come funzionano i meccanismi complessi e, soprattutto, come non funzionano.

Le VPN sono la porta d'accesso alla rete delle organizzazioni, quindi le vulnerabilità presenti in queste apparecchiature influiscono notevolmente sulle aziende.
Le VPN sono la porta d'accesso alla rete delle organizzazioni, quindi le vulnerabilità presenti in queste apparecchiature influiscono notevolmente sulle aziende.
  • Ben Barnea, ricercatore di Akamai, ha individuato diverse vulnerabilità nel FortiOS di Fortinet.
  • Un criminale non autenticato può attivare vulnerabilità tali da causare attacchi DoS e RCE
  • La vulnerabilità DoS è semplice da sfruttare e causa l'interruzione dell'apparecchiatura Fortigate.
  • Riteniamo che la vulnerabilità RCE sia difficile da sfruttare.
  • Le vulnerabilità, a cui sono stati assegnati i codici CVE-2024-46666 e CVE-2024-46668, sono state divulgate in modo responsabile su Fortinet.
  • Fortinet ha pubblicato le vulnerabilità scoperte da Barnea il 14 gennaio 2025 e i dispositivi con versioni aggiornate di FortiOS sono protetti da queste vulnerabilità.

Introduzione

Negli ultimi anni, le soluzioni VPN sono state interessate da molte vulnerabilitàcritiche, che sono state sfruttate in rete dai criminali. Alcune di queste vulnerabilità sono estremamente facili da sfruttare e hanno un impatto devastante poiché causano attacchi RCE sulle apparecchiature VPN visibili su Internet. Una volta all'interno della rete, i criminali possono muoversi lateralmente per guadagnare l'accesso ai dati sensibili, alla proprietà intellettuale e ad altre risorse di alto valore.

Oltre allo sfruttamento iniziale delle vulnerabilità nelle VPN, Ori David, un ricercatore di Akamai, ha anche documentato alcune tecniche post-sfruttamento e ha mostrato che un server VPN violato potrebbe consentire ai criminali di ottenere facilmente il controllo su altre risorse critiche nella rete.

Sfortunatamente, i ricercatori che si occupano di sicurezza delle apparecchiature VPN trovano difficoltà ad iniziare la loro ricerca poiché i firmware non sempre sono facilmente disponibili e sono protetti con meccanismi di crittografia dai vendor. Tuttavia, poiché le apparecchiature VPN sono l'obiettivo principale da sfruttare, superare questi sistemi di protezione può sicuramente risultare remunerativo per i criminali.

In questo blog, esamineremo il processo di ricerca esaminando la soluzione VPN di Fortinet: come acquisire il firmware, eseguire la decrittografia, configurare un debugger e, infine, individuare le vulnerabilità.

Alcune delle ricerche presentate in questo post non sono nuove perché sono state condotte eccellenti ricerche su FortiOS già da Optistream, Bishop Fox, Assetnote, Lexfo e molti altri.  Abbiamo aggiornato le loro ricerche iniziali con la versione di FortiOS più recente poiché Fortinet, spesso, cambia i metodi di crittografia e decrittografia, rendendo più difficile analizzare il dispositivo.

Acquisizione di un'immagine del firmware

Le VPN sono sempre state vendute come apparecchiature fisiche separate, il che potrebbe complicare la loro acquisizione e l'estrazione del loro firmware. Oggi, comunque, è molto più comune trovare apparecchiature VPN implementate in una macchina virtuale (VM).

Fortunatamente per noi, Fortinet offre una VM di prova da poter scaricare dal proprio sito web dopo aver effettuato la registrazione (Figura 1). La VM è limitata: è consentita solo una CPU limitata a 2 GB di RAM.

Fortinet offre una VM di prova da poter scaricare dal proprio sito web dopo aver effettuato la registrazione (Figura 1). Fig. 1. Prove di VM disponibili per il download

Creazione di un ambiente di debug

La VM fornita contiene due punti di interesse: (1) un'immagine di avvio, insieme ad un'immagine kernel (denominata flatkc), e (2) un file system rootfs crittografato che contiene la maggior parte dei file interessanti. all'interno del file system, una volta decrittografato, possiamo trovare un file binario denominato init all'interno della directory /bin/.

La maggior parte dei file binari della VM sono stati statisticamente compilati in questo file binario. Due interessanti file binari presenti nella directory /bin/init sono il file SSLVPND e il server web di gestione. Tratteremo di questi file binari più avanti nel post.

Vogliamo creare un ambiente con una shell completa, non la CLI vincolata che abbiamo ricevuto da Fortinet. Inoltre, vogliamo disporre di un file binario gdb in grado di consentirci di eseguire facilmente il debug dei file binari.

Per creare un ambiente di questo tipo, dobbiamo effettuare le seguenti operazioni:

  1. Decomprimere il file CPIO compresso in formato GZIP (un formato di archiviazione dei file)
  2. Decrittografare il file rootfs con lo script di Bishop Fox
  3. Estrarre l'archivio bin.tar.xz
  4. Applicare una patch al controllo di integrità della directory /bin/init
  5. Convertire flatkc in ELF tramite vmlinux-to-elf
  6. Individuare l'indirizzo di fgt_verify_initrd nell'IDA per applicare una patch in fase di runtime allo scopo di disattivare ulteriori controlli di integrità
  7. Inserire una busybox e un gdb statisticamente compilati all'interno della directory /bin/
  8. Compilare uno stub in grado di creare un server telnet; sovrascrivere la directory /bin/smartctl con questo stub
  9. Comprimere la cartella /bin/
  10. Creare un pacchetto con il file rootfs e crittografarlo
  11. Aggiungere un padding alla fine del file rootfs crittografato
  12. Sostituire il file rootfs nella VMDK con una VM Ubuntu dell'helper (che monta la VMDK)

I passaggi illustrati nella Figura 2 consentono di creare una VM modificata con funzionalità di debug. Poiché il controllo di integrità del file rootfs non riesce, il kernel interrompe l'esecuzione. Pertanto, dobbiamo applicare una patch al kernel (flatkc), quindi applicare anche una patch al codice di verifica dell'integrità del bootloader oppure dobbiamo applicare una patch dinamica al controllo di integrità del kernel. Abbiamo deciso di seguire il secondo approccio.

I passaggi illustrati nella Figura 2 consentono di creare una VM modificata con funzionalità di debug. Fig. 2. Applicazione di patch a FortiGate per un ambiente di ricerca

Abbiamo tentato di utilizzare la funzione di debug della VM di VMware modificando il file VMDK, configurando così un debugger GDB che sarà disponibile una volta eseguita la macchina. Sfortunatamente, abbiamo riscontrato un problema con l'implementazione di questa funzione durante l'esecuzione su un sistema Hyper-V, che si è bloccato non appena ha incontrato un breakpoint, probabilmente perché la funzione di debug del kernel non è stata implementata completamente durante l'esecuzione su un sistema Hyper-V.

Tentativo di creare una VM con QEMU

Dopo aver tentato varie volte di eseguire il debug del kernel con VMware, abbiamo deciso di tentare di creare una VM con QEMU. Abbiamo statisticamente creato la VM modificata eseguendo operazioni simili, tranne per il fatto di dover convertire continuamente i formati di file qcow2 in VMDK e viceversa.

Per eseguire il debug del kernel durante l'utilizzo di QEMU, possiamo fornire il flag -s al qemu-system. Infine, abbiamo eseguito la VM, collegato il GDB e aggiunto un breakpoint per sovrascrivere il controllo di integrità, quindi abbiamo ottenuto la CLI (Figura 3).

Infine, abbiamo eseguito la VM, collegato il GDB e aggiunto un breakpoint per sovrascrivere il controllo di integrità, quindi abbiamo ottenuto la CLI (Figura 3). Fig. 3. VM funzionante con CLI

Dopo aver configurato le impostazioni della connessione di rete e aver ricevuto un IP valido dal server DHCP, possiamo eseguire il file binario smartctl modificato, che stampa il contenuto della directory attuale, esegue il comando Linux ID e apre una sessione telnet busybox (Figura 4).

Dopo aver configurato le impostazioni della connessione di rete e aver ricevuto un IP valido dal server DHCP, possiamo eseguire il file binario smartctl modificato, che stampa il contenuto della directory attuale, esegue il comando Linux ID e apre una sessione telnet busybox (Figura 4). Fig. 4. Attivazione della backdoor

Infine, la Figura 5 mostra come abbiamo effettuato la connessione al server telnet appena creato.

La Figura 5 mostra come abbiamo effettuato la connessione al server telnet appena creato. Fig. 5. Connessione alla shell

Tutto qui? No. 

Come possiamo vedere nella Figura 6, non abbiamo una licenza valida, che ci impedisce di interagire con il pannello amministrativo.

Come possiamo vedere nella Figura 6, non abbiamo una licenza valida, che ci impedisce di interagire con il pannello amministrativo. Fig. 6. Errore relativo alla licenza

Aggirare il problema (?)

Innanzitutto, abbiamo pensato che la licenza non fosse valida perché superava il limite di 1 CPU e la RAM era di 2,048 MB. Quindi, potevamo scegliere tra due opzioni: Accettare questo limite e creare una VM molto lenta oppure aggirare il problema.

Dopo qualche passaggio di decompilazione, abbiamo trovato la funzione upd_vm_check_license, che viene richiamata periodicamente in un daemon (Figura 7). Questa funzione controlla che la RAM e il numero delle CPU del sistema non superino i limiti stabiliti.

Dopo qualche passaggio di decompilazione, abbiamo trovato la funzione upd_vm_check_license, che viene richiamata periodicamente in un daemon (Figura 7). Fig. 7. Il codice decompilato, che è responsabile dei vincoli della VM

Dopo aver bypassato le restrizioni modificando il valore restituito delle stringhe num_max_CPUs() e max_allowed_RAM() in modo dinamico, ora possiamo disporre di una VM potente e senza vincoli, il cui avvio consentirà di visualizzare un minor numero di errori, anche se verrà comunque visualizzato un messaggio di errore relativo alla licenza non valida.

Dopo aver impiegato troppo tempo nel decompilare la funzione di verifica delle licenza e nel pensare a quali scelte di vita ci ha condotto, alla fine abbiamo scoperto che la licenza utilizza il numero seriale della macchina virtuale, costruita tramite l'UID del SMBIOS. Poiché non ci è stato fornito un QEMU, abbiamo usato un valore NULL, pertanto è stato creato il numero seriale "FGVMEV0000000000". Dopo aver fornito l'UUID del SMBIOS a QEMU con il seguente flag:

  -smbios type=1,manufacturer=t1manufacturer,product=t1product,version=t1version,serial=t1serial,
  uuid=25359cc8-5fe7-4d50-ab82-9fd15ecaf221,sku=t1sku,family=t1family

siamo riusciti finalmente ad avviare la macchina virtuale e a ricevere una licenza valida. 

Nuova versione. Nuova crittografia. PERCHÉ?

A questo punto, possiamo disporre di un ambiente di debug funzionante. Abbiamo iniziato a cercare il server web di gestione e abbiamo individuato alcune vulnerabilità che descriveremo più avanti in questo post. Quando abbiamo trovato queste vulnerabilità, abbiamo notato che Fortinet aveva rilasciato la versione 7.4.4 di FortiOS.

Quindi, abbiamo pensato di verificare se le vulnerabilità fossero presenti anche nella nuova release, ma non essendo riusciti a decrittografare i file rootfs della VM, ci siamo resi conto che la crittografia era completamente diversa e, persino, più difficile da decifrare. Questa volta, abbiamo deciso di cercare di decrittografare il nuovo file rootfs, ma non di creare un ambiente di debug perché l'obiettivo principale, a questo punto, era verificare che le vulnerabilità fossero ancora presenti.

Quindi, innanzitutto, vogliamo descrivere il vecchio metodo (precedente alla versione 7.4.4) per decrittografare il file rootfs:

1. Il kernel verifica l'integrità del file rootfs e, se è valido, continua con il passaggio successivo

2. Il kernel richiama il comando fgt_verifier_key_iv, che calcola una chiave e l'IV nel modo seguente:

a. Chiave: sha256() dei dati globali

b. IV: sha256() di un'altra parte degli stessi dati globali; quindi, tronca il risultato a 16 byte

3. Viene decrittografato il file rootfs tramite Chacha20 con la chiave e l'IV qui sopra

Ora, guardiamo il nuovo algoritmo (Figura 8):

1. Il codice di decrittografia calcola la chiave e l'IV mediante sha256() di un buffer di dati globale, come nell'algoritmo precedente

2. ChaCha esegue la decrittografia di un blocco di memoria tramite la chiave e l'IV; questo blocco di memoria è un ASN1 che rappresenta una chiave privata RSA

3. Dalla chiave privata RSA, viene preso il valore d,n e viene decrittografato un blocco di dati che presenta almeno 256 byte del firmware crittografato tramite la formula nota M = Cd mod N

4. Dal blocco di dati, vengono presi i seguenti valori:

  • 16 byte, che sono nonce + contatore 

  • 32 byte (chiave)  

5. Viene eseguita la decrittografia AES in modalità CTR, con la chiave e nonce + contatore; il codice utilizza un'aggiunta CTR personalizzata

6. L'aggiunta è il risultato dell'operazione di XORing dei nibble del valore (nonce + contatore)

 

Nuovo algoritmo (Figura 8): Fig. 8. Diagramma di flusso sulla decrittografia del firmware

Per aumentare la complessità di questo algoritmo di decrittografia multifase, il nuovo flatkc non ha più simboli, quindi è più difficile scrivere strumenti in grado di decrittografare automaticamente il firmware, ad esempio, individuare i dati globali utilizzati per decrittografare ChaCha e la chiave privata RSA crittografata.

Dopo aver effettuato tutti i passaggi descritti sopra, possiamo visualizzare il file rootfs (Figura 9).

Dopo aver effettuato tutti i passaggi descritti sopra, possiamo visualizzare il file rootfs (Figura 9). Fig. 9. Compressione del file rootfs in formato tar

Questa volta, non abbiamo creato un ambiente modificato. perché questa operazione richiede la creazione di un archivio rootfs che deve essere decrittografato come descritto in precedenza. In alternativa, è possibile impostare dei breakpoint in modo dinamico e sovrascrivere la memoria con un file rootfs decrittografato.

Decompilazione del server web di amministrazione

A questo punto, possiamo finalmente passare alla decompilazione del server web di amministrazione, che, basato su Apache, in generale, non è accessibile su Internet (al contrario dell'interfaccia sslvpn, che, invece, è accessibile su Internet).

Quando si apre il file di configurazione httpd, possiamo notare alcuni comandi di posizione che puntano gli URL ai rispettivi handler (Figura 10).

Quando si apre il file di configurazione httpd, possiamo notare alcuni comandi di posizione che puntano gli URL ai rispettivi handler (Figura 10). Fig. 10. Snippet del file di configurazione httpd

Quindi, possiamo cercare una delle stringhe dell'handler nel file binario per individuare la tabella dell'handler (Figura 11).

Quindi, possiamo cercare una delle stringhe dell'handler nel file binario per individuare la tabella dell'handler (Figura 11). Fig. 11. Elenco degli handler mostrati nell'IDA

Nell'intento di individuare vulnerabilità non autenticate, abbiamo deciso di focalizzarci sul api_authentication-handler che è accessibile tramite l'URL /api/v2/authentication.

Prima di approfondire il processo di decompilazione, si consiglia di creare alcune strutture Apache e una struttura di connessione nell'IDA per facilitare l'operazione (Figure 12 e 13).

  struct __attribute__((aligned(8))) _request_rec
{
    apr_pool_t *pool;
    conn_rec *connection;
    void *server;
    _request_rec *next;
    _request_rec *prev;
    _request_rec *main;
    char *the_request;
    int assbackwards;
    int proxyreq;
    int header_only;
    int proto_num;
    char *protocol;
    const char *hostname;
    unsigned __int64 request_time;
    const char *status_line;
    int status;
    enum http_methods method_number;
    const char *method;
    unsigned __int64 allowed;
    void *allowed_xmethods;
    void *allowed_methods;
    unsigned __int64 sent_bodyct;
    unsigned __int64 bytes_sent;
    unsigned __int64 mtime;
    const char *range;
    unsigned __int64 clength;
    int chunked;
    int read_body;
    int read_chunked;
    unsigned int expecting_100;
    void *kept_body;
    void *body_table;
    unsigned __int64 remaining;
    unsigned __int64 read_length;
    void *headers_in;
    void *headers_out;
    void *err_headers_out;
    void *subprocess_env;
    void *notes;
    const char *content_type;
    const char *handler;
    const char *content_encoding;
    void *content_languages;
    char *vlist_validator;
    char *user;
    char *ap_auth_type;
    char *unparsed_uri;
    char *uri;
    char *filename;
    char *canonical_filename;
    char *path_info;
    char *args;
    int used_path_info;
    int eos_sent;
    void *per_dir_config;
    void *request_config;
    void *log;
    const char *log_id;
    void *htaccess;
    void *output_filters;
    void *input_filters;
    void *proto_output_filters;
    void *proto_input_filters;
    int no_cache;
    int no_local_copy;
    void *invoke_mtx;
    apr_uri_t parsed_uri;
    apr_finfo_t finfo;
    void *useragent_addr;
    char *useragent_ip;
    void *trailers_in;
    void *trailers_out;
    char *useragent_host;
    int double_reverse;
    unsigned __int64 bnotes;
};

Fig. 12. Strutture Apache

  struct __attribute__((aligned(8))) conn_rec
{
    apr_pool_t *pool;
    void *base_server;
    void *vhost_lookup_data;
    apr_sockaddr_t *local_addr;
    sockaddr *client_addr;
    char *client_ip;
    char *remote_host;
    char *remote_logname;
    char *local_ip;
    char *local_host;
    __int64 id;
    void *conn_config;
    void *notes;
    void *input_filters;
    void *output_filters;
    void *sbh;
    void *bucket_alloc;
    void *cs;
    int data_in_input_filters;
    int data_in_output_filters;
    unsigned __int32 clogging_input_filters : 1;
    __int32 double_reverse : 2;
    unsigned int aborted;
    ap_conn_keepalive_e keepalive;
    int keepalives;
    void *log;
    const char *log_id;
    conn_rec *master;
    int outgoing;
};

Fig. 13. Una struttura di connessione

Al momento di decompilare l'handler, abbiamo innanzitutto decompilato l'handler del metodo POST, denominato api_login_handler. La funzione recupera i parametri di login dalla richiesta richiamando il comando api_login_parse_param. Questa funzione tenta di analizzare i dati POST a seconda dell'intestazione Content-Type:

  1. Se impostata su "multipart/form-data", la richiesta presenta un modulo HTML

  2. In caso contrario, vengono letti i dati POST non formattati

La seconda opzione è alquanto semplice, quindi ci siamo focalizzati maggiormente sulla prima parte. Esaminando il codice decompilato, abbiamo notato subito una stringa di debug che ci ha indicato la libreria libapreq (Figura 14).

Esaminando il codice decompilato, abbiamo notato subito una stringa di debug che ci ha indicato la libreria libapreq (Figura 14). Fig. 14. Stringa che indica l'utilizzo della libreria libapreq da parte del codice

Poiché la libreria libapreq è una libreria Apache open source, non abbiamo avuto (praticamente) alcun motivo per cercare vulnerabilità nel codice decompilato anziché nel codice sorgente. Quindi, innanzitutto abbiamo dovuto individuare la versione della libreria. Dopo vari tentativi, siamo riusciti a restringere la versione individuando una stringa presente nel file binario e un commit specifico, ma è stato rimosso un commit dopo (Figura 15).

Innanzitutto, abbiamo dovuto individuare la versione della libreria. Dopo vari tentativi, siamo riusciti a restringere la versione individuando una stringa presente nel file binario e un commit specifico, ma è stato rimosso un commit dopo (Figura 15). Fig. 15. Confronto tra il codice decompilato del file binario e il commit del codice sorgente che rimuove la stringa

Sorprendentemente, la libreria presente nel file binario è la versione disponibile più vecchia a partire da marzo 2000 (Figura 16).

Sorprendentemente, la libreria presente nel file binario è la versione disponibile più vecchia a partire da marzo 2000 (Figura 16). Fig. 16. La versione ristretta della libreria libapreq

Vulnerabilità

Fortinet utilizza il modulo quasi come 25 anni fa, ad eccezione di alcuni piccolissimi cambiamenti apportati per motivi di ottimizzazione. Quando abbiamo osservato questo comportamento per la prima volta, abbiamo pensato di non avere alcuna possibilità di non trovare vulnerabilità nel codice risalente al 2000. E avevamo ragione!

Prima di dare uno sguardo alle vulnerabilità, vogliamo spiegare lo scopo e l'uso della libreria. Apreq è una libreria Apache utilizzata per gestire i dati della richiesta del client. Un modo comune per ricevere i dati da un utente è tramite i moduli HTML. I dati del modulo compilato possono essere trasmessi al server tramite diversi metodi di codifica, ma i modi comuni sono application/x-www-form-urlencoded e multipart/form-data.

Quando viene utilizzato il metodo multipart/form-data, il client (di solito, il browser) sceglie un testo arbitrario come confine tra i diversi campi dei dati nel modulo. Il confine viene specificato tramite un'intestazione HTTP e viene utilizzato anche per indicare la fine dei dati del modulo (Figura 17).

  POST /foo HTTP/1.1
  Content-Length: 68137
  Content-Type: multipart/form-data; boundary=ExampleBoundaryString

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

  Description input value
  --ExampleBoundaryString
  Content-Disposition: form-data; name="myFile"; filename="foo.txt"
  Content-Type: text/plain

  [content of the file foo.txt chosen by the user]
  --ExampleBoundaryString--

Figura 17. Esempio di un modulo del limite HTTP (fonte)

Ora, esaminiamo alcune vulnerabilità che abbiamo individuato, tra cui la scrittura OOB (Out-Of-Bounds) del byte NULL, la copia lineare, il DoS del dispositivo, il DoS del server web e la lettura OOB.

La scrittura OOB del byte NULL

Dopo aver riempito il buffer interno ed esaminato il confine, la funzione multipart_buffer_read restituisce la stringa tra la posizione corrente e il confine individuato. Il bug consiste nel fatto che il confine non si trova all'inizio del buffer interno, ma restituisce la stringa dopo aver rimosso gli ultimi due caratteri che, presumibilmente, chiudono la linea ("\r\n"). Il codice suppone, erroneamente, che la stringa abbia restituito una lunghezza maggiore di 2.

Nella Figura 18, retval è la stringa restituita e start rappresenta la sua lunghezza, che equivale a 1. In questo caso, anche blen equivalente a start, che viene diminuito di due unità fino a raggiungere il valore -1. Pertanto, è possibile scrivere NULL un byte prima del buffer.

Nella Figura 18, retval è la stringa restituita e start rappresenta la sua lunghezza, che equivale a 1. In questo caso, anche blen equivalente a start, che viene diminuito di due unità fino a raggiungere il valore -1. Pertanto, è possibile scrivere NULL un byte prima del buffer. Fig. 18. Vulnerabilità che conduce alla scrittura OOB del valore NULL prima del buffer

Tentativi di sfruttamento

Anche se un overflow di un byte (ma anche di un bit) può essere sufficiente per effettuare l'esecuzione di un codice, riteniamo che questa vulnerabilità sia poco sfruttabile da un punto di vista pratico. Innanzitutto, possiamo scrivere solo un byte NULL e solo un byte prima del buffer. Il buffer viene allocato sull'heap; pertanto, sono disponibili due opzioni:

1. Il buffer è il primo buffer allocato sul nodo dell'heap. In questo caso, troveremo i metadati del nodo dell'heap prima del nostro buffer allocato (Figura 19).

Il buffer è il primo buffer allocato sul nodo dell'heap. In questo caso, troveremo i metadati del nodo dell'heap prima del nostro buffer allocato (Figura 19). Fig. 19. Struttura che rappresenta un nodo dell'heap nella memoria di Apache

Sovrascriviamo un byte del puntatore endp. Questa operazione non influirà sul valore del puntatore perché andremo a sovrascrivere il byte più elevato del puntatore a causa dell'architettura endian. Poiché la VM è un sistema x64, questo byte sarà sempre uguale a 0.

2. Se si è verificata un'allocazione prima della nostra, potremo sovrascrivere un byte di dati. Sfortunatamente, come visto nell'esempio precedente, nella maggior parte dei casi, avremo un puntatore alla fine della struttura, un padding o una stringa C che presenta già una terminazione NULL.

Abbiamo individuato un elemento interessante: la struttura C multipart_buffer (Figura 2).

Abbiamo individuato un elemento interessante: la struttura C multipart_buffer (Figura 2). Fig. 20. La struttura C multipart_buffer

In questo caso, abbiamo pensato di far diventare negativa l'ultima variabile della struttura, buffer_len, tramite la vulnerabilità precedente, quindi di cambiarla da negativa in un valore molto positivo con questa vulnerabilità (sovrascrivendo il byte MSB e contrassegnando il numero come negativo).

Anche se questo approccio ci era sembrato interessante, ha presentato due problemi:

1. La struttura viene creata solo una volta, durante la creazione del parser del modulo, quindi non è possibile colpire facilmente questo elemento.

2. Una volta utilizzata la vulnerabilità precedente, abbiamo limitato la lettura della lunghezza nella funzione di riempimento. Pertanto, una volta terminata la funzione di riempimento, il valore self->length risulta pari a 0 dopo la lettura dei dati POST della richiesta completa. Quando, successivamente, il codice richiama il comando multipart_buffer_read, non troverà il confine nel buffer avanzato (poiché abbiamo fatto comparire il puntatore finale prima del suo inizio) e, poiché il valore self->length è pari a 0, verrà visualizzato un avviso relativo ad un errore di caricamento.

Abbiamo pensato di tentare di sfruttare questa vulnerabilità in una race condition, ma, se esaminiamo la modalità MPM (MultiProcessing Mode) di Apache, verrà visualizzato quanto riportato nella Figura 21.

Abbiamo pensato di tentare di sfruttare questa vulnerabilità in una race condition, ma, se esaminiamo la modalità MPM (MultiProcessing Mode) di Apache, verrà visualizzato quanto riportato nella Figura 21. Fig. 21. Verifica della modalità MPM di Apache

In tal caso, Apache si biforca in più processi e ciascuno di essi gestisce una richiesta. Abbiamo anche notato che questi processi non sono costituiti da più thread., quindi non è possibile sfruttare questa vulnerabilità in una race condition.

Copia lineare

Nella stessa funzione, multipart_buffer_read, se il codice non trova il confine (start uguale a -1), restituisce solo la parte del buffer interno: (byte - boundary_length). L'errore qui consiste nel fatto che i byte sono impostati sul valore costante di 5120, mentre la lunghezza del confine può essere molto più grande (al limite della lunghezza dell'intestazione).

Pertanto, inviando un campo in cui il confine non si trova nel primo segmento, se la lunghezza del confine è maggiore di 5120, possiamo rendere negativo il valore blen. In tal modo, l'impostazione del codice self->buffer si troverà prima del buffer e il codice self->buffer_len sarà equivalente ad un valore maggiore (Figura 22).

In tal modo, l'impostazione del codice self->buffer si troverà prima del buffer e il codice self->buffer_len sarà equivalente ad un valore maggiore (Figura 22). Fig. 22. Vulnerabilità causata da un underflow di un numero intero

Tentativi di sfruttamento

Questa vulnerabilità è diversa dalla vulnerabilità precedente perché, questa volta, poiché il valore start è negativo (il confine non è stato trovato), non riusciamo ad ottenere il codice che scrive un byte NULL.

Il parametro blen fa parte della funzione multipart_buffer_read_body, che richiama e riceve il valore blen come output.

La Figura 23 mostra come il parametro blen sia utilizzato due volte:

1. Se è il primo buffer creato, la stringa ricevuta dalla funzione multipart_buffer_read viene duplicata tramite il parametro blen. In tal caso, Apache visualizza un errore OOM (Out-Of-Memory) e il codice viene interrotto. Questo metodo può essere usato per sferrare un attacco DoS.

2. Se è il secondo segmento, il segmento precedente e quello attuale vengono uniti tramite la funzione my_join, che richiama memcpy con un numero negativo, determinando una copia lineare.

La Figura 23 mostra come il parametro blen sia utilizzato due volte Fig. 23. I due diversi flussi presenti nella funzione multipart_buffer_read_body

Nota: alcuni di voi potrebbero aver notato un altro bug presente in questo codice, ossia il fatto che il valore old_len viene aggiornato per apparire come blen anziché old_len + blen, causando un troncamento dei dati degli utenti.

Lo sfruttamento di questa copia lineare è molto difficile, se non proprio impossibile. Innanzitutto, non possiamo "interrompere" la copia lineare, che è un memcpy di grandi dimensioni. In secondo luogo, non è costituito da più thread, quindi non possiamo sovrascrivere un oggetto che verrà usato contemporaneamente in un altro thread. Quindi, l'unica opzione possibile sembra essere quella di sfruttare l'handler del segnale se non riesce ad uscire correttamente.

DoS del dispositivo

Questa vulnerabilità non si trova nella libreria, ma nel codice di Fortinet che utilizza la libreria.

Se l'utente carica un file tramite il modulo, che viene specificato con un'intestazione Content-Disposition all'interno dei dati POST (vedere la Figura 17), viene creato un nuovo file all'interno della cartella /tmp/ con il nome file /tmp/uploadXXXXXX, dove le X sono caratteri casuali.

Per ogni file caricato, viene creata un'apposita struttura, che viene inserita in un elenco di file caricati con collegamenti. Il bug si presenta alla fine dell'analisi quando viene eliminato solo il file presente nel primo nodo dell'elenco con i collegamenti. In tal modo, un criminale può avviare un attacco riempiendo la cartella /tmp/. Poiché la cartella /tmp/ è un file system tmpfs, i dati vengono archiviati nella RAM. Quindi, si verifica un OOM del sistema completo, che causa il blocco del dispositivo.

Solo con il riavvio, il dispositivo ritorna al suo normale utilizzo (anche se non sempre). In uno dei nostri tentativi, abbiamo in qualche modo bloccato la connessione di rete al dispositivo e, anche dopo il riavvio del dispositivo, la funzionalità di rete non si è ripristinata correttamente, quindi non siamo riusciti ad utilizzare il dispositivo o ad effettuare la connessione al dispositivo.

Tentativi di sfruttamento

Questa vulnerabilità è molto semplice da sfruttare perché richiede solo l'invio ripetuto di richieste con un modulo contenente più file, che, dopo poco tempo, causa il blocco del dispositivo (Figura 24).

perché richiede solo l'invio ripetuto di richieste con un modulo contenente più file, che, dopo poco tempo, causa il blocco del dispositivo (Figura 24). Fig. 24. Riempimento della RAM dell'apparecchiatura VPN con file non eliminati fino a determinare, eventualmente, una vulnerabilità DoS causata dalla memoria insufficiente

DoS del server web

Nella funzione multipart_buffer_headers, è presente una vulnerabilità minore, che richiama la funzione multipart_buffer_fill per far riempire il buffer interno e cercare una doppia linea che termina nel buffer interno.

Il bug consiste nel fatto che il codice non verifica che il buffer interno sia valido dopo aver richiamato la funzione multipart_buffer_fill. Se il client ha interrotto la connessione mentre la funzione multipart_buffer_fill stava aspettando l'input dell'utente, il buffer viene impostato su NULL e viene interrotto il riferimento a NULL (Figura 25).

Se il client ha interrotto la connessione mentre la funzione multipart_buffer_fill stava aspettando l'input dell'utente, il buffer viene impostato su NULL e viene interrotto il riferimento a NULL (Figura 25). Fig. 25. Accesso al buffer interno senza verificare che sia valido

Tentativi di sfruttamento

Anche questa vulnerabilità è molto semplice da sfruttare perché un criminale può creare più thread per stabilire una connessione e inviare la richiesta che poi bloccherà il dispositivo. Poiché la modalità MPM di Apache non offre performance eccellenti, il server non sarà in grado di gestire più interruzioni né altri client mentre subisce l'attacco.

Lettura OOB

La libreria utilizza un buffer interno che viene riempito regolarmente. Una volta riempito il buffer interno, il numero di byte da leggere viene calcolato nel modo seguente:

1. Calcolare (bytes_requested - current_internal_buffer_len)

2. Aggiungere la lunghezza del confine + 2 (per "r\n\r\n")

3. Calcolare il valore minimo tra questo risultato e i byte disponibili da leggere dai dati POST

La vulnerabilità si trova nella funzione multipart_buffer_read. Se il confine si trova all'inizio del buffer interno (e non si tratta del confine che segna la fine del modulo), il puntatore del buffer interno viene avanzato dopo il confine e vengono aggiunti altri 2 byte al termine della linea.

L'errore qui consiste nel fatto che un criminale può far sì che le dimensioni del buffer interno siano esattamente uguali a quelle del confine, causando così l'avanzamento del codice due byte dopo la fine del buffer interno (Figura 26). Per ottenere questo risultato, possiamo "limitare" il calcolo del buffer interno inserendo un numero di byte inferiore a quello del valore boundary_length + bytes_requested.

L'errore qui consiste nel fatto che un criminale può far sì che le dimensioni del buffer interno siano esattamente uguali a quelle del confine, causando così l'avanzamento del codice due byte dopo la fine del buffer interno (Figura 26). Fig. 26. Avanzamento del buffer interno dopo la sua fine

Tentativi di sfruttamento

Come abbiamo visto nella Figura 26, il codice restituisce il valore NULL. Eppure, come abbiamo osservato nel bug precedente, il codice presente nella funzione multipart_buffer_headers accede direttamente al buffer interno e non al valore restituito, quindi, il codice cerca i caratteri "\r\n" dopo il buffer e li restituisce come intestazione.

Se viene utilizzata la libreria apreq nell'handler di login, il codice legge i campi del modulo, ma non li invia nuovamente al client, quindi, in tal caso, non possiamo usare la vulnerabilità OOB per esfiltrare informazioni. La libreria viene anche utilizzata più volte nel server web, ma sembra che sia disponibile solo per gli utenti autenticati, quindi, un criminale che dispone di credenziali utente con privilegi minimi potrebbe usarla, al massimo, come vulnerabilità di escalation dei privilegi leggendo dalla memoria le credenziali utente con privilegi elevati.

SSLVPND

Il daemon SSLVPND è responsabile della gestione del componente SSL-VPN di Fortinet ed è accessibile su Internet. La libreria apreq viene utilizzata anche nel daemon SSLVPND. La libreria utilizzata qui è basata sulla stessa versione di apreq precedente, a cui sono state apportate alcune modifiche. Tutte le vulnerabilità descritte qui sopra, ad eccezione della vulnerabilità DoS del dispositivo, sono presenti anche nel daemon SSLVPND.

Sfortunatamente, non siamo riusciti ad attivare la libreria apreq nel daemon SSLVPND, quindi non possiamo confermare che queste vulnerabilità siano accessibili agli utenti autenticati o se potrebbero essere sfruttate nel daemon SSLVPND.

Riepilogo

I criminali, spesso, prendono di mira le VPN perché sono accessibili su Internet. In questo blog, abbiamo esaminato l'esempio di un approccio alla ricerca su un'apparecchiatura VPN. Anche se non sono state identificate vulnerabilità critiche, riteniamo che altre vulnerabilità stiano solo aspettando di essere individuate.

Le VPN sono la porta d'accesso alla rete delle organizzazioni, quindi le vulnerabilità presenti in queste apparecchiature influiscono notevolmente sulle aziende. Speriamo che questo blog possa anche incoraggiare altri ricercatori che si occupano di sicurezza ad esaminare le vulnerabilità delle VPN.



Akamai Wave Blue

scritto da

Ben Barnea

February 11, 2025

Akamai Wave Blue

scritto da

Ben Barnea

Ben Barnea è Security Researcher in Akamai con interesse e competenze nella conduzione di ricerche sulle vulnerabilità e sulla sicurezza di basso livello in varie architetture, tra cui quelle in Windows e Linux, nonché nell'IoT (Internet of Things) e nei dispositivi mobili. Adora scoprire come funzionano i meccanismi complessi e, soprattutto, come non funzionano.