Security

Total.js Directory Traversal (CVE-2019-8903) discovery and exploitation

Hot-bypass di un hot-fix

Riccardo Krauter Gennaio, 2020
Total.js Directory Traversal (CVE-2019-8903) discovery and exploitation

In questo articolo spieghiamo come siamo stati in grado di trovare e sfruttare una vulnerabilità di Directory Traversal o Path Traversal sul framework Total.js.

The ../ (dot dot slash)

In ogni directory ci sono sempre due riferimenti: il . e il .. che rappresentano rispettivamente la directory stessa e la directory padre. Il pattern ../ assume quindi un significato particolare quando si parla di navigazione del filesystem, referenziando la directory padre. Per fare un esempio il path /root/../etc/passwd è equivalente al path /etc/passwd. Infatti, partendo dalla directory /root si fa riferimento alla directory padre / mediante il pattern ../.
Questa caratteristica della navigazione del filesystem mediante l'uso del ../ può essere usata per eseguire attacchi di tipo Path Traversal.
Un esempio esplicativo di un Web Server vulnerabile è riportato nell'immagine seguente

Directory Traversal

Lo scenario mostrato in figura rappresenta una richiesta HTTP di un client con un payload di Path Traversal. Il server risolve il path tenendo conto dei caratteri ../ inviati dal client uscendo dalla Web Root Directory.

Analisi ed intuizioni

Dando una prima occhiata veloce al codice è possibile notare che moltissime funzionalità sono sviluppate da zero senza ausilio di librerie terze. Una tra tutte è la gestione delle richieste HTTP riguardanti file statici. In generale, quando si ha a che fare con un Web Server o un Framework che si occupano della gestione dei file statici, provare attacchi di Path Traversal è un must. Per gli appassionati del settore, un esempio appropriato è la macchina Smasher di Hack The Box, dove una delle vulnerabilità da sfruttare è proprio di Directory Traversal mediante URI path.

Primi tentativi e fallimenti

Come accennato in precedenza una delle funzionalità che offre Total.js è la gestione dei file statici. Normalmente ci si aspetta che l'applicazione (sia essa un Web Server o un framework) usi una directory predefinita dalla quale "pescare" i file statici richiesti dagli utenti. Total.js cerca i file statici nella directory "public/" e, se trova il file richiesto, ne legge il contenuto e lo serve in risposta.

Filesystem

Per verificare se è possibile eseguire un attacco di Path Traversal, la prima cosa da provare è modificare il path della richiesta HTTP mediante un proxy. Il payload più semplice è il seguente:

GET /../../../../../../../../../../../../../etc/passwd HTTP/1.1
Host: localhost:8000
User-Agent: my-UA
Accept: */*
...

Un altro pattern che può tornare utile, anche in caso di sostituzione (non ricorsiva) della stringa ../, è il seguente:

/....//....//....//....//....//....//....//....//....//....//etc/passwd

Purtroppo, anche dopo diversi tentativi mediante fuzzing con liste come quelle del noto FuzzDB, la risposta era sempre la stessa: 404 Not Found!

Analisi statica e discovery della vulnerabilità

Analizzando il sorgente del framework ci siamo imbattuti nella seguente porzione di codice:

// all HTTP static request are routed to directory-public
static_url: '',
static_url_script: '/js/',
static_url_style: '/css/',
static_url_image: '/img/',
static_url_video: '/video/',
static_url_font: '/fonts/',
static_url_download: '/download/',
static_url_components: '/components.',
static_accepts: {
  flac: true, jpg: true, jpeg: true, png: true, gif: true,
  ico: true, js: true, css: true, txt: true, xml: true, woff: true,
  woff2: true, otf: true, ttf: true, eot: true, svg: true, zip: true,
  rar: true, pdf: true, docx: true, xlsx: true, doc: true, xls: true,
  html: true, htm: true, appcache: true, manifest: true, map: true,
  ogv: true, ogg: true, mp4: true, mp3: true, webp: true, webm: true,
  swf: true, package: true, json: true, md: true, m4v: true, jsx: true,
  heif: true, heic: true, ics: true
}

Il codice suggerisce che le richieste per file statici (notare il commento all'inizio del codifica) sono solo quelle che hanno un'estensione compresa nella lista static_accepts.

Nonostante usare il file "passwd" per il fuzzing in assenza di firewall sia in generale una buona idea, tutte le prove fatte in precedenza non avrebbero mai potuto funzionare in quanto il file "passwd" non ha estensione.

A questo punto abbiamo ripetuto il test, ma questa volta usando un file con un'estensione presente nella lista presente nel codice (sempre al di fuori della directory consentita "public/").
Questo è stato il risultato:

Directory Traversal - request and response

L'attacco ha funzionato! L'applicazione ha navigato il filesystem usando il ../ iniettato nella URI e di conseguenza ha restituito il contenuto di un file al di fuori della directory consentita!

L'impatto di questa vulnerabilità è importante. Infatti, un attaccante è in grado di estrarre il contenuto di file sensibili come, per esempio, i file di configurazione, ecc.

Far tesoro degli errori

Iniziare un Penetration Test in modo aggressivo, accanendosi sull'applicazione con un fuzzer, e limitarsi a provare a estrarre un singolo file che potrebbe non esistere, non è la strategia migliore. In presenza di un Web Application Firewall (WAF) o anche solo di un firewall, a fare richieste su path noti come "/etc/passwd" si rischia addirittura di essere "bannati".

Un tentativo che forse valeva la pena fare fin dall'inizio è il seguente:

  1. scegliere un file che notoriamente il server tratta come file statico, per esempio robots.txt
  2. conoscendo il nome della web root, "public/" in questo caso, costruire un payload tipo: GET /../public/robots.txt

In questo modo avremmo potuto verificare immediatamente la presenza della vulnerabilità.

Hot-Fix e Hot-Bypass

Volendo fare Responsible Disclosure, ci siamo subito messi in contatto con il team che si occupa di manutenere il framework. La risposta è stata repentina! Hanno immediatamente rilasciato un hot-fix sulla repository git.

Di seguito la porzione di codice relativa al fix:

...
// Stops path travelsation outside of "public" directory
// A potential security issue
if (req.uri.pathname.indexOf('./') !== -1) {
  req.$total_status(404);
  return;
}
...

Non ci abbiamo messo troppo tempo a trovare un bypass...

Il codice introdotto dal fix si occupa di restituire 404 Not Found per le richieste che contengono il pattern ./. Inoltre, dopo il blocco di codice riportato, viene eseguita una URL decode di req.uri.pathname. Quindi è stato sufficiente fare la URL encode del payload e ottenere il seguente vettore d'attacco:

GET /.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/var/www/html/index.html

Questo nuovo payload non contiene il pattern ./, quindi non otteniamo la fastidiosa 404, e viene successivamente decodificato dal server come segue:

GET /../../../../../../../../../../../../../../var/www/html/index.html

Timeline

Data Cosa
12 febbraio 2019 La vulnerabilità è stata segnalata al team di Total.js.
12 febbraio 2019 Total.js rilascia il primo hot-fix.
13 febbraio 2019 l'hot-fix è stato bypassato.
14 febbraio 2019 Total.js rilascia il fix "definitivo".
18 febbraio 2019 Il MITRE assegna il CVE-2019-8903.

Summary

In questo articolo abbiamo visto come inventarsi un sistema per gestire le richieste HTTP non sia semplice e privo di rischi. Inoltre, l'eccessiva fretta nel rilasciare una patch non ha portato alcun beneficio. Noi invece abbiamo imparato che a volte pensare fuori dagli schemi può portare a individuare vulnerabilità che non ci si aspetta più di vedere su un framework.

Riferimenti

Articoli correlati

Security

Articoli in evidenza

Approfondimenti

UNISCITI A NOI. INVIA LA TUA CANDIDATURA

Certimeter Group crede nei valori, nella passione e nella professionalità delle persone.