SQL injection: cos’è e come proteggersi
Le SQL injection rappresentano un grosso pericolo per i tradizionali database relazionali e le informazioni conservate al suo interno. È quindi essenziale una protezione completa contro questi accessi esterni non autorizzati, resi possibili da falle di sicurezza.
Che cos’è una SQL injection?
Con SQL injection (in italiano “iniezione SQL”) si intende lo sfruttamento di una vulnerabilità nei database relazionali, che utilizzano il linguaggio di query SQL per l’inserimento dei dati. In questo caso l’hacker approfitta di quegli input degli utenti che non sono filtrati correttamente e in cui sono presenti dei metacaratteri quali trattino doppio, virgolette o punto e virgola. Questi caratteri possiedono funzioni particolari per l’interprete SQL e consentono la modifica esterna dei comandi eseguiti. Spesso una SQL injection si presenta correlata a programmi in PHP e ASP che dispongono di vecchie interfacce. Qui, a volte, gli input non sono filtrati nel modo giusto e rappresentano perciò il target perfetto per un attacco hacker.
Con un uso mirato di caratteri di funzione, un utente non autorizzato può iniettare in questo modo altri comandi SQL e manipolare i record così da poter modificare, leggere o eliminare i dati. Nei casi più gravi è persino possibile che, sfruttando 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 funzionano gli attacchi al database
Visto che i database vulnerabili vengono rilevati in fretta e gli attacchi SQL injection possono essere eseguiti altrettanto facilmente, 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 vulnerabilità recenti delle applicazioni coinvolte nel processo di gestione dei dati, prendendo di mira soprattutto quelle conosciute da tempo. Per spiegare meglio come funziona esattamente una SQL injection, ti forniamo due esempi sulla base di due metodi tipici.
Esempio 1: accesso tramite un input utente filtrato erroneamente
Solitamente, per poter accedere a un database, un utente deve prima di tutto autenticarsi. A questo scopo esistono script che, ad esempio, presentano un modulo di login, comprensivo di nome utente e password. L’utente compila il modulo e lo script effettua contestualmente i controlli per sapere se nel database esistono i record corrispondenti. Per impostazione predefinita viene creata nel database una tabella con il nome users
e le colonne username
e password
. In un’applicazione web qualsiasi le corrispondenti righe degli script (pseudocodice di Python) per l’accesso al server web potrebbero 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)
pythonUn hacker ha ora la possibilità di manipolare 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'"
pythonIn 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 amministratore o amministratrice, può fare tutte le modifiche che vuole ai record. In alternativa, si può manipolare allo stesso modo il campo del nome utente.
Esempio 2: rilevare i dati tramite manipolazione ID
Interrogare le informazioni di un database tramite ID è un metodo pratico e utilizzato oggigiorno, che rappresenta però anche una porta d’accesso per una SQL injection. Così un server web sa, ad esempio tramite un inserimento ID trasmesso nell’URL, quali informazioni debba richiamare dal database. Lo script PHP corrispondente 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);
}
?>
phpCi si aspetta che l’URL si presenti come …/script.php?id=22
. Nel caso rappresentato il record della tabella sarebbe stato richiamato con l’ID “22”. Se una persona non autorizzata avesse ora la possibilità di manipolare 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’interrogazione possano essere letti.
SELECT * FROM tabella WHERE id=22 OR 1=1;
sqlCome fanno gli hacker a scoprire i database vulnerabili?
In linea di massima, tutti i siti e le applicazioni web che utilizzano database SQL senza istruzioni preparate (prepared statement) o altri meccanismi di protezione possono essere soggetti a iniezioni SQL. Le vulnerabilità scoperte non rimangono a lungo segrete nel vasto World Wide Web ed esistono perciò pagine che informano sulle vulnerabilità attuali e svelano anche ai cyber criminali come possono trovare i siti affetti da queste vulnerabilità facendo una ricerca su Google. Se un sito restituisce messaggi di errore SQL dettagliati, i criminali possono sfruttarli per identificare potenziali vulnerabilità. Così si può semplicemente aggiungere un apostrofo a un URL con un parametro ID (come nell’esempio seguente):
[Nome dominio].it/news.php?id=5‘
Un sito attaccabile 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 automatizzare la richiesta e le correlate SQL injection.
Come proteggere 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 componenti coinvolti, cioè del server, delle singole applicazioni e del Database Management System (sistema di gestione di base di dati).
Primo passaggio: controllare gli input automatici delle applicazioni
Quando si elaborano input provenienti da applicazioni esterne o integrate, è essenziale convalidare e filtrare i valori trasmessi per evitare le iniezioni SQL.
1. Controllare i tipi di dati
Ogni input deve corrispondere al tipo di dati previsto. Ad esempio, se è richiesto un input numerico, una semplice validazione in PHP può essere simile a questa:
if (filter_var($input, FILTER_VALIDATE_INT) === false) {
throw new InvalidArgumentException("Input non valido");
}
phpControlli simili dovrebbero essere implementati per stringhe, valori sotto forma di data o altri formati specifici.
2. Filtrare i caratteri speciali
I caratteri speciali possono causare falle di sicurezza, soprattutto 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 contenenti dettagli tecnici sul database o sul sistema dovrebbero 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.");
php4. Utilizzare istruzioni preparate
Un modo sicuro per prevenire le iniezioni SQL è quello di utilizzare le prepared statement già menzionate. In questo caso, i comandi SQL e i parametri vengono trasmessi separatamente, 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();
phpIl sistema di gestione dei database garantisce automaticamente che l’input venga elaborato in modo sicuro.
Secondo passaggio: occuparsi di una protezione del server completa
La sicurezza del server, sul quale esegui il tuo DBMS, ricopre ovviamente un ruolo importante nella prevenzione contro le SQL injection. Prima di tutto va rafforzato il sistema operativo in base allo schema conosciuto:
- Installa o attiva solo applicazioni e servizi che sono importanti per il funzionamento del database.
- Elimina tutti gli account utente che non ti servono.
- Assicurati che tutti gli aggiornamenti importanti del sistema e dei programmi siano installati.
- Applica il principio del privilegio minimo per garantire che gli utenti e i servizi ricevano solo le autorizzazioni minime richieste.
A seconda dei requisiti di sicurezza del progetto web, è necessario prendere in considerazione ulteriori misure di protezione:
- Intrusion Detection Systems (IDS) e Intrusion Prevention Systems (IPS): questi sistemi lavorano con diversi metodi di riconoscimento per individuare tempestivamente gli attacchi sul server, emettere i relativi avvisi e anche attivare automaticamente, nel caso degli IPS, le giuste contromisure.
- Application Layer Gateway (ALG): ALG controlla e filtra il traffico dati tra le applicazioni e i browser direttamente sul livello di applicazione.
- Web Application Firewall (WAF): WAF protegge specificamente le applicazioni web da SQL injection e cross-site scripting (XSS) bloccando o attenuando le richieste sospette.
- Approccio a fiducia zero: questo moderno approccio alla sicurezza garantisce che ogni accesso, indipendentemente dalla sua origine, venga controllato e verificato prima di essere consentito.
- Regolamenti firewall e segmentazione della rete: queste misure sono fondamentali per ridurre al minimo la superficie di attacco a lungo termine.
- Verifiche periodiche della sicurezza informatica e test di penetrazione: aiutano a riconoscere e chiudere le vulnerabilità in una fase iniziale.
Terzo passaggio: rafforzare il database e utilizzare codici sicuri
Come il tuo sistema operativo, il database dovrebbe essere ripulito di tutti quei fattori irrilevanti e aggiornato regolarmente. A questo scopo, elimina tutte le procedure memorizzate che non ti servono e disattiva tutti i servizi inutili e gli account utente. Imposta un account database speciale, da utilizzare esclusivamente per l’accesso dal web e dotato dei permessi minimi.
Per quanto riguarda le prepared statement, è caldamente consigliato di non utilizzare il modulo PHP mysql
(eliminato da PHP 7) e di scegliere invece mysqli
o PDO (PHP Data Objects). In questo modo puoi proteggerti ulteriormente 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();
phpInoltre, le password non dovrebbero mai essere memorizzate direttamente in un database o interrogate come testo normale. Al contrario, si deve usare un metodo di hashing come password_hash()
in combinazione con password_verify()
per proteggere 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.";
}
phpBobby Tables: una spiegazione a fumetti di SQL injection
Sulla pagina bobby-tables.com viene affrontata la tematica degli input utente del database non sicuri. Il fumetto mostra una madre che riceve una chiamata dalla scuola di suo figlio, amorevolmente 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 comunicarle che il tentativo di creare un record per Robert nel database della scuola, ha portato all’eliminazione completa di tutti i dati del figlio sul loro database. La madre di Robert si mostra tutt’altro che apprensiva 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 conseguenze fatali che possono avere gli inserimenti di utenti non verificati nei database.