SQL injection

Una SQL injection (abbreviata anche come "SQLi") è una vulnerabilità web che permette all'attaccante di interferire con l'esecuzione delle query che l'applicazione esegue su un database. La presenza di tale vulnerabilità può permettere a un attaccante di accedere a dati che normalmente dovrebbero rimanere nascosti, come informazioni private degli utenti, modificarli e cancellarli. Nel peggiore dei casi questa vulnerabilità porta alla compromissione dell'intera applicazione. In alcuni contesti può addirittura portare a esecuzione di codice arbitrario sul server, fornendo accesso completo del sistema all'attaccante.

Da cosa origina una SQL injection?

Le SQL injection hanno origine da un trattamento insicuro degli input utente utilizzati all'interno delle query. Un attaccante può sfruttare questo mancato controllo per iniettare codice SQL valido all'interno di una query. Facciamo ora qualche esempio di codice vulnerabile a SQL injection nei linguaggi di programmazione più comuni.

JAVA

Prendiamo questo codice di esempio.

// ottieni lo username dai query parameters
String username = request.getParameter("username");
//  crea uno statement dalla connesione con il database
Statement statement = connection.createStatement();  
// crea una query insicura tramite concatenazione di stringhe
String query = "SELECT secret FROM Users WHERE (username = '" + username + "' AND NOT role = 'admin')";
// esegui la query e ritorna il valore
ResultSet result = statement.executeQuery(query);

La costruzione della query così fatta, tramite semplice concatenazione di stringhe, rende l'applicazione vulnerabile a SQL injection: l'attaccante potrebbe fornire come username questa stringa

' OR username = 'admin' ); -- 

trasformando la query eseguita in:

SELECT secret FROM Users WHERE (username = '' OR username = 'admin' ); -- ' AND NOT role = 'admin')

che restituirebbe il secret dell'admin aggirando il controllo sul ruolo. Questo poiché in SQL i caratteri -- indicano l'inizio di un commento, che "taglia" fuori il controllo. Notare che l'input dell'utente viene interpretato parte del comando SQL.

PHP

<?php

$db = new SQLite3('test.db');

if (strlen($_GET['id']) < 1) {
  echo 'Usage: ?id=1';
} else {
  $count = $db->querySingle('select * from secrets where id = ' . $_GET['id']);
}

In questo caso accade sostanzialmente la stessa cosa: il contenuto del parametro id viene concatenato alla stringa della query senza alcun tipo di controllo. L'attaccante potrebbe fornire come id 1 OR 1=1 ottenendo così tutte le informazioni relative ai secrets di chiunque. Questo poiché aggiungendo la condizione 1=1 l'attaccante rende la condizione sempre vera, aggirando perciò il controllo sull'id.

Come identificare se un'applicazione è vulnerabile a SQL injection

È possibile testare se un'applicazione è vulnerabile a SQL injection inserendo all'interno dei parametri delle richieste espressioni o caratteri che hanno un significato sintattico in SQL e verificare il comportamento in risposta. Ad esempio:

  • l'inserimento del carattere ' potrebbe causare degli errori nella query rilevabili dall'utente
  • l'inserimento di condizioni tautologiche come OR 1=1 o di condizioni sempre false come OR 1=2 può generare dei comportamenti indice di vulnerabilità nell'applicazione

Ci sono inoltre strumenti che automatizzano la ricerca e lo spruttamento di questa vulnerabilità, di cui il più famoso è sicuramente sqlmap.

Mitigazioni

È possibile porre rimedio a questa vulnerabilità utilizzando delle query parametriche, dette anche prepared statements. Queste permettono di creare delle query contenenti input forniti dall'utente senza doversi preoccupare che questi modifichino la semantica della query. Ad esempio:

PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();

Queste possono essere utilizzate per tutte le query in cui l'input utente rappresenta dei dati. Nel caso in cui l'input debba essere utilizzato come nome di colonna, di tabella o simili è necessario sanitizzarlo, facendo uso di una whitelist. È comunque sconsigliato di utilizzare input utente in questa maniera.

NoSQL injection

Questo tipo di vulnerabilità può verificarsi anche con l'utilizzo di database NoSQL, come ad esempio MongoDB. Le NoSQL injection si possono categorizzare in due gruppi:

  • Syntax injection: si verificano quando l'input dell'attaccante è in grado di essere interpretato come sintassi NoSQL, similarmente a come accade con le SQL injection viste fino a ora.
  • Operator injection: si verificano quando l'attaccante è in grado di utilizzare degli operatori NoSQL per modificare il comportamento delle query.

Syntax injection

Esattamente come nel caso delle SQLi, l'attaccante può verificare il comportamento del server in risposta all'inserimento di caratteri facenti parte della sintassi del linguaggio NoSQL relativo al DB che sta tentando di attaccare, e procedere inserendo tautologie o contraddizioni al fine di modificare o aggirare controlli.

Operator injection

I database NoSQL spesso utilizzano i query operators, che permettono di specificare delle condizioni che i dati devono rispettare per essere inclusi nel risultato della query. Alcuni esempi di operatori utilizzati in MongoDB sono:

  • $where
  • $ne
  • $in
  • $regex

Mitigazioni

I metodi di mitigazione dipendono da quale NoSQL DB stiamo utilizzando, e sono solitamente esplicitati nella sezione riguardante la sicurezza della documentazione del DB stesso. In generale, i metodi per mitigare questa vulnerabilità rimangono sostanzialmente gli stessi: sanitizzare l'input utente con una whitelist, oppure utilizzare query parametriche.