Weak password or weak hash function
A new tragedy
Dario Ragno
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.
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
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.
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.
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.
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!
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:
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.
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:
robots.txt
GET /../public/robots.txt
In questo modo avremmo potuto verificare immediatamente la presenza della vulnerabilità.
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
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. |
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.
Gemma Contini
Daniele Scanu
Fabio Carretto
Certimeter Group crede nei valori, nella passione e nella professionalità delle persone.