Le SQL injection rap­pre­sen­ta­no un grosso pericolo per i tra­di­zio­na­li database re­la­zio­na­li e le in­for­ma­zio­ni con­ser­va­te al suo interno. È quindi es­sen­zia­le una pro­te­zio­ne completa contro questi accessi esterni non au­to­riz­za­ti, resi possibili da falle di sicurezza.

Che cos’è una SQL injection?

Con SQL injection (in italiano “iniezione SQL”) si intende lo sfrut­ta­men­to di una vul­ne­ra­bi­li­tà nei database re­la­zio­na­li, che uti­liz­za­no il lin­guag­gio di query SQL per l’in­se­ri­men­to dei dati. In questo caso l’hacker ap­pro­fit­ta di quegli input degli utenti che non sono filtrati cor­ret­ta­men­te e in cui sono presenti dei me­ta­ca­rat­te­ri quali trattino doppio, vir­go­let­te o punto e virgola. Questi caratteri pos­sie­do­no funzioni par­ti­co­la­ri per l’in­ter­pre­te SQL e con­sen­to­no la modifica esterna dei comandi eseguiti. Spesso una SQL injection si presenta correlata a programmi in PHP e ASP che di­spon­go­no di vecchie in­ter­fac­ce. Qui, a volte, gli input non sono filtrati nel modo giusto e rap­pre­sen­ta­no perciò il target perfetto per un attacco hacker.

Con un uso mirato di caratteri di funzione, un utente non au­to­riz­za­to può iniettare in questo modo altri comandi SQL e ma­ni­po­la­re i record così da poter mo­di­fi­ca­re, leggere o eliminare i dati. Nei casi più gravi è persino possibile che, sfrut­tan­do questo metodo, l’hacker riesca ad avere accesso alla riga di comando del sistema e giunga così all’intero server del database.

Esempi di SQL injection: come fun­zio­na­no gli attacchi al database

Visto che i database vul­ne­ra­bi­li vengono rilevati in fretta e gli attacchi SQL injection possono essere eseguiti al­tret­tan­to fa­cil­men­te, questo metodo rientra tra i preferiti su scala mondiale. Così i cyber criminali agiscono mettendo in atto diversi schemi di attacco e sfruttano a proprio vantaggio le vul­ne­ra­bi­li­tà recenti delle ap­pli­ca­zio­ni coinvolte nel processo di gestione dei dati, prendendo di mira so­prat­tut­to quelle co­no­sciu­te da tempo. Per spiegare meglio come funziona esat­ta­men­te una SQL injection, ti forniamo due esempi sulla base di due metodi tipici.

Esempio 1: accesso tramite un input utente filtrato er­ro­nea­men­te

So­li­ta­men­te, per poter accedere a un database, un utente deve prima di tutto au­ten­ti­car­si. A questo scopo esistono script che, ad esempio, pre­sen­ta­no un modulo di login, com­pren­si­vo di nome utente e password. L’utente compila il modulo e lo script effettua con­te­stual­men­te i controlli per sapere se nel database esistono i record cor­ri­spon­den­ti. Per im­po­sta­zio­ne pre­de­fi­ni­ta viene creata nel database una tabella con il nome users e le colonne username e password. In un’ap­pli­ca­zio­ne web qualsiasi le cor­ri­spon­den­ti righe degli script (pseu­do­co­di­ce di Python) per l’accesso al server web po­treb­be­ro essere le seguenti:

uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
python

Un hacker ha ora la pos­si­bi­li­tà di ma­ni­po­la­re in modo mirato il campo della password tramite SQL injection, inserendo, per esempio, password’ OR 1=‘1, che genera la seguente richiesta SQL:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
python

In questo modo ottiene l’accesso completo all’intera tabella utente del database, perché l’input della password è sempre vero (1='1'). Se ora effettua l’accesso come am­mi­ni­stra­to­re o am­mi­ni­stra­tri­ce, può fare tutte le modifiche che vuole ai record. In al­ter­na­ti­va, si può ma­ni­po­la­re allo stesso modo il campo del nome utente.

Esempio 2: rilevare i dati tramite ma­ni­po­la­zio­ne ID

In­ter­ro­ga­re le in­for­ma­zio­ni di un database tramite ID è un metodo pratico e uti­liz­za­to og­gi­gior­no, che rap­pre­sen­ta però anche una porta d’accesso per una SQL injection. Così un server web sa, ad esempio tramite un in­se­ri­men­to ID trasmesso nell’URL, quali in­for­ma­zio­ni debba ri­chia­ma­re dal database. Lo script PHP cor­ri­spon­den­te sarebbe:

<?php
    $mysqli = new mysqli("localhost", "nome utente", "password", "database");
    $id = intval($_GET['id']);
    $result = $mysqli->query("SELECT * FROM tabella WHERE id=$id");
    while ($row = $result->fetch_assoc()) {
        echo print_r($row, true);
    }
?>
php

Ci si aspetta che l’URL si presenti come …/script.php?id=22. Nel caso rap­pre­sen­ta­to il record della tabella sarebbe stato ri­chia­ma­to con l’ID “22”. Se una persona non au­to­riz­za­ta avesse ora la pos­si­bi­li­tà di ma­ni­po­la­re questo URL e inoltra, invece, al web server la richiesta …/script.php?id=22+OR+1=1, ne deriva che tutti i dati risultati dall’in­ter­ro­ga­zio­ne possano essere letti.

SELECT * FROM tabella WHERE id=22 OR 1=1;
sql

Come fanno gli hacker a scoprire i database vul­ne­ra­bi­li?

In linea di massima, tutti i siti e le ap­pli­ca­zio­ni web che uti­liz­za­no database SQL senza istru­zio­ni preparate (prepared statement) o altri mec­ca­ni­smi di pro­te­zio­ne possono essere soggetti a iniezioni SQL. Le vul­ne­ra­bi­li­tà scoperte non rimangono a lungo segrete nel vasto World Wide Web ed esistono perciò pagine che informano sulle vul­ne­ra­bi­li­tà attuali e svelano anche ai cyber criminali come possono trovare i siti affetti da queste vul­ne­ra­bi­li­tà facendo una ricerca su Google. Se un sito re­sti­tui­sce messaggi di errore SQL det­ta­glia­ti, i criminali possono sfrut­tar­li per iden­ti­fi­ca­re po­ten­zia­li vul­ne­ra­bi­li­tà. Così si può sem­pli­ce­men­te ag­giun­ge­re un apostrofo a un URL con un parametro ID (come nell’esempio seguente):

[Nome dominio].it/news.php?id=5‘

Un sito at­tac­ca­bi­le risponde con un messaggio di errore che suona più o meno così:

Query failed: You have an error in your SQL Syntax …

In italiano: “Richiesta fallita: la sintassi SQL inserita è errata…”. Con metodi simili si possono anche ottenere il numero delle colonne, il nome delle tabelle e delle colonne, la versione SQL o persino i nomi utente e le password. Con diversi strumenti si può anche au­to­ma­tiz­za­re la richiesta e le correlate SQL injection.

Come pro­teg­ge­re il tuo database da una SQL injection

Puoi mettere in atto diverse misure per evitare attacchi di SQL injection sul tuo database. Per fare ciò dovrai occuparti di tutti i com­po­nen­ti coinvolti, cioè del server, delle singole ap­pli­ca­zio­ni e del Database Ma­na­ge­ment System (sistema di gestione di base di dati).

Primo passaggio: con­trol­la­re gli input au­to­ma­ti­ci delle ap­pli­ca­zio­ni

Quando si elaborano input pro­ve­nien­ti da ap­pli­ca­zio­ni esterne o integrate, è es­sen­zia­le con­va­li­da­re e filtrare i valori trasmessi per evitare le iniezioni SQL.

1. Con­trol­la­re i tipi di dati

Ogni input deve cor­ri­spon­de­re al tipo di dati previsto. Ad esempio, se è richiesto un input numerico, una semplice va­li­da­zio­ne in PHP può essere simile a questa:

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Input non valido");
}
php

Controlli simili do­vreb­be­ro essere im­ple­men­ta­ti per stringhe, valori sotto forma di data o altri formati specifici.

2. Filtrare i caratteri speciali

I caratteri speciali possono causare falle di sicurezza, so­prat­tut­to in contesti SQL o HTML. Un metodo sicuro è usare htmlspecialchars() per l’input HTML e PDO::quote() per le query SQL.

3. Evitare i messaggi di errore

I messaggi di errore diretti con­te­nen­ti dettagli tecnici sul database o sul sistema do­vreb­be­ro essere evitati. Si consiglia invece un output generico come:

echo "Si è verificato un errore. Riprova più tardi";
error_log("Si è verificato un errore inatteso. Vedi il log di sistema per maggiori dettagli.");
php

4. Uti­liz­za­re istru­zio­ni preparate

Un modo sicuro per prevenire le iniezioni SQL è quello di uti­liz­za­re le prepared statement già men­zio­na­te. In questo caso, i comandi SQL e i parametri vengono trasmessi se­pa­ra­ta­men­te, in modo che il codice dannoso non possa essere eseguito. Un esempio adeguato in PHP con PDO (PHP Data Objects) è il seguente:

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();
php

Il sistema di gestione dei database ga­ran­ti­sce au­to­ma­ti­ca­men­te che l’input venga elaborato in modo sicuro.

Secondo passaggio: occuparsi di una pro­te­zio­ne del server completa

La sicurezza del server, sul quale esegui il tuo DBMS, ricopre ov­via­men­te un ruolo im­por­tan­te nella pre­ven­zio­ne contro le SQL injection. Prima di tutto va raf­for­za­to il sistema operativo in base allo schema co­no­sciu­to:

  • Installa o attiva solo ap­pli­ca­zio­ni e servizi che sono im­por­tan­ti per il fun­zio­na­men­to del database.
  • Elimina tutti gli account utente che non ti servono.
  • As­si­cu­ra­ti che tutti gli ag­gior­na­men­ti im­por­tan­ti del sistema e dei programmi siano in­stal­la­ti.
  • Applica il principio del pri­vi­le­gio minimo per garantire che gli utenti e i servizi ricevano solo le au­to­riz­za­zio­ni minime richieste.

A seconda dei requisiti di sicurezza del progetto web, è ne­ces­sa­rio prendere in con­si­de­ra­zio­ne ulteriori misure di pro­te­zio­ne:

  • Intrusion Detection Systems (IDS) e Intrusion Pre­ven­tion Systems (IPS): questi sistemi lavorano con diversi metodi di ri­co­no­sci­men­to per in­di­vi­dua­re tem­pe­sti­va­men­te gli attacchi sul server, emettere i relativi avvisi e anche attivare au­to­ma­ti­ca­men­te, nel caso degli IPS, le giuste con­tro­mi­su­re.
  • Ap­pli­ca­tion Layer Gateway (ALG): ALG controlla e filtra il traffico dati tra le ap­pli­ca­zio­ni e i browser di­ret­ta­men­te sul livello di ap­pli­ca­zio­ne.
  • Web Ap­pli­ca­tion Firewall (WAF): WAF protegge spe­ci­fi­ca­men­te le ap­pli­ca­zio­ni web da SQL injection e cross-site scripting (XSS) bloccando o at­te­nuan­do le richieste sospette.
  • Approccio a fiducia zero: questo moderno approccio alla sicurezza ga­ran­ti­sce che ogni accesso, in­di­pen­den­te­men­te dalla sua origine, venga con­trol­la­to e ve­ri­fi­ca­to prima di essere con­sen­ti­to.
  • Re­go­la­men­ti firewall e seg­men­ta­zio­ne della rete: queste misure sono fon­da­men­ta­li per ridurre al minimo la su­per­fi­cie di attacco a lungo termine.
  • Verifiche pe­rio­di­che della sicurezza in­for­ma­ti­ca e test di pe­ne­tra­zio­ne: aiutano a ri­co­no­sce­re e chiudere le vul­ne­ra­bi­li­tà in una fase iniziale.

Terzo passaggio: raf­for­za­re il database e uti­liz­za­re codici sicuri

Come il tuo sistema operativo, il database dovrebbe essere ripulito di tutti quei fattori ir­ri­le­van­ti e ag­gior­na­to re­go­lar­men­te. A questo scopo, elimina tutte le procedure me­mo­riz­za­te che non ti servono e disattiva tutti i servizi inutili e gli account utente. Imposta un account database speciale, da uti­liz­za­re esclu­si­va­men­te per l’accesso dal web e dotato dei permessi minimi.

Per quanto riguarda le prepared statement, è cal­da­men­te con­si­glia­to di non uti­liz­za­re il modulo PHP mysql (eliminato da PHP 7) e di scegliere invece mysqli o PDO (PHP Data Objects). In questo modo puoi pro­teg­ger­ti ul­te­rior­men­te anche con un codice sicuro. Ad esempio, una richiesta mysqli sicura si presenta così:

$mysqli = new mysqli("localhost", "utente", "password", "database");
if ($mysqli->connect_error) die("Connessione non andata a buon fine");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
    echo "Login effettuato con successo";
} else {
    echo "Dati di accesso errati";
}
$stmt->close();
$mysqli->close();
php

Inoltre, le password non do­vreb­be­ro mai essere me­mo­riz­za­te di­ret­ta­men­te in un database o in­ter­ro­ga­te come testo normale. Al contrario, si deve usare un metodo di hashing come password_hash() in com­bi­na­zio­ne con password_verify() per pro­teg­ge­re in modo ottimale le password. Una soluzione sicura potrebbe essere la seguente:

$mysqli = new mysqli("localhost", "utente", "password", "database");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
    echo "Login effettuato con successo!";
} else {
    echo "Nome utente errato o password errata.";
}
php

Bobby Tables: una spie­ga­zio­ne a fumetti di SQL injection

Sulla pagina bobby-tables.com viene af­fron­ta­ta la tematica degli input utente del database non sicuri. Il fumetto mostra una madre che riceve una chiamata dalla scuola di suo figlio, amo­re­vol­men­te chiamato “Little Bobby Tables”. Risponde di sì alla domanda se suo figlio si chiami realmente Robert’); DROP TABLE Students;– – e scopre infine che il vero motivo della chiamata è quello di co­mu­ni­car­le che il tentativo di creare un record per Robert nel database della scuola, ha portato all’eli­mi­na­zio­ne completa di tutti i dati del figlio sul loro database. La madre di Robert si mostra tutt’altro che ap­pren­si­va e si augura, invece, che la scuola abbia imparato dall’errore e che pulisca in futuro gli input nel database.

Il fumetto mostra in modo efficace le con­se­guen­ze fatali che possono avere gli in­se­ri­men­ti di utenti non ve­ri­fi­ca­ti nei database.

Vai al menu prin­ci­pa­le