Product Catalog 8 per WordPress plug-in a rischio


Esistono diversi plug-in di WordPress che permettono di realizzare in pochi clic un sito di e-commerce. Uno dei più diffusi fino a qualche tempo fa era Product Catalog 8. Al momento, questo plug-in è stato sospeso perché lo sviluppatore non ha più il tempo di aggiornarlo e questo lo rende un facile bersaglio per i pirati. Secondo le statistiche di WordPress, infatti, la maggior parte degli utenti non è particolarmente solerte nell’aggiornare il proprio CMS e i relativi plug-in. Il risultato è che diversi siti rischiano di essere vulnerabili ai vari bug che affliggono questo ormai datato plug-in.

Un’iniezione SQL
C’è un bug che risalta fra tutti per la sua pericolosità, perché offre la possibilità di una SQL Injection. Ma forse dobbiamo fare qualche passo indietro. WordPress è un’applicazione PHP basata su database SQL. Di solito si utilizza un server MySQL oppure PostgreSQL, ma si può anche ricorrere ad un file SQLite. Ad ogni modo, è poco rilevante, perché in ciascun caso il risultato è che tutte le informazioni del sito Web vengono memorizzate nel database. Questo include i testi, la struttura delle pagine, l’interfaccia, e persino le immagini e i file allegati (il loro URL). Ma, soprattutto, i dati sugli utenti, i commenti e le password di accesso, inclusa quelle di amministrazione. Ciò significa che è estremamente importante proteggere questo database e impedire che un malintenzionato possa accedervi in lettura e, soprattutto, in scrittura. Perché potendo scrivere nel database un pirata potrebbe, ad esempio, cancellare delle tabelle o modificare la password di amministratore per accedere al sito e modificarlo a proprio piacimento. I database SQL sono molto comodi per i programmatori perché si basano su delle query, ovvero dei comandi. Per fare qualche esempio: esiste un comando per leggere le tabelle, uno per crearle e uno per distruggerle. Basta inviare tali comandi tramite PHP e si può manipolare il database. Siccome i comandi sono di fatto delle stringhe di testo, sono molto comode da realizzare e da adattare alle varie esigenze. Il problema nasce quando un comando deve essere realizzato utilizzando delle informazioni fornite dall’utente. Ad esempio, il comando DROP TABLE gianni provvede a cancellare la tabella chiamata gianni. Si può decidere di inserire in una pagina HTML una casella di testo tramite la quale l’utente possa indicare il nome della tabella da eliminare. Poi PHP non deve far altro che concatenare DROP TABLE con il valore della casella di testo e il gioco è fatto. Il problema è che in questo modo se l’utente commette un errore, il comando può essere snaturato. Addirittura, se l’utente è un malintenzionato, può scrivere nella casella di testo un codice che sostituisce il comando con un altro, in modo da “iniettare” nel server SQL un comando malevolo al posto di quello previsto originariamente. Per evitare che ciò accada, si deve controllare ogni contributo dell’utente, per assicurarsi che non contenga caratteri speciali che possano modificare la query.

Questione di codice
Questo controllo, però, non avviene nel codice del plug-in Product Catalog: quest’estensione è realizzata con una serie di file PHP. Tra questi, c’è il file includes/ajax-functions.php, che contiene una serie di funzioni. In particolare, si nota la funzione UpdateCategoryList, che può essere chiamata fornendo alla pagina wp-admin/admin-ajax.php il suo nome nel campo action fornito tramite HTTP POST (è quindi sufficiente creare un semplice form HTML contente questo codice:

<input type="text" name="action" value="UpdateCategoryList">
.

Il codice di questa funzione è il seguente:

function UpdateCategoryList() {
global $wpdb, $subcategories_table;

global $wpdb;
$table = $subcategories_table;
$catid = $_POST['selectedCategory'];

if($catid !== '0') {

$get_items = $wpdb->get_results( "SELECT * FROM $table WHERE subcategory_category = $catid ORDER BY subcategory_name ASC" );
echo json_encode($get_items);
}
else {
$get_items = "";
echo json_encode($get_items);
}

die();

}

Si può immediatamente notare che la funzione riceve tramite HTTP POST un testo chiamato selectedCategory, che viene subito inserito nella variabile $catid. Il problema è che poi tale variabile viene inserita direttamente nella query SQL, ovvero quel testo che inizia con il comando SELECT. Siccome non c’è alcun controllo sul contenuto della variabile $catid e non viene applicata alcuna funzione di escape per eliminare potenziali caratteri pericolosi. Ciò significa che un utente malintenzionato può facilmente sfruttare l’occasione per far eseguire a PHP una query a propria discrezione. In poche parole, ad un pirata è sufficiente scrivere questo codice:

<form method="post" action="http://www.sitoweb.com/wp-admin/admin-ajax.php">
<input type="text" name="selectedCategory" value="0 UNION SELECT 1,2,3,4,5,6 FROM wp_terms WHERE term_id=1">
<input type="text" name="action" value="UpdateCategoryList">
<input type="submit" value="Send">
</form>

per produrre un form tramite il quale inviare una query arbitraria. Il pirata può quindi modificare parti sensibili del database, inclusi gli hash delle password degli utenti amministratori.

Come correre ai ripari
Se questo plug-in è presente sulla nostra installazione di WordPress, la soluzione migliore consiste nel disabilitarlo e passare ad un altro plug-in simile ma costantemente aggiornato. Questo caso ci insegna quanto sia importante mantenere aggiornato il CMS, perché essere presi di mira dai pirati è davvero molto facile se si utilizzano applicazioni contenenti bug. Se siamo dei programmatori, invece, possiamo evitare le SQL Injection in due modi. Il primo, e più semplice, consiste nel ricorrere ad una funzione di escape, che rimuova tutti i simboli speciali che potrebbero modificare la natura della query che abbiamo scritto:

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('". $safe_variable. "')");

La funzione di escape fornita da PHP si chiama mysql_real_escape_string, ed è valida nella maggioranza dei casi. L’alternativa, è rappresentata dall’uso delle funzioni MYSQLi invece della comuni MYSQL. In particolare, le query possono essere “preparate” con una funzione chiamata, per l’appunto mysqli.prepare.
Questo è un esempio di utilizzo:

<?php
$mysqli = new mysqli("server", "username", "password", "database_name");
$unsafe_variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
$stmt->bind_param("s", $unsafe_variable);
$stmt->execute();
$stmt->close();
$mysqli->close();
?>

Questa opzione è solitamente la migliore, ma ha un difetto: supporta soltanto MySQL. Se si vuole utilizzare un differente database SQL, si deve provvedere a sviluppare un livello di astrazione utilizzando PDO, il che complica abbastanza le cose.

Fonte: http://punto-informatico.it/