Un modulo di login, un campo di ricerca o una pagina che mostra i dettagli di un ordine possono sembrare funzionalità del tutto innocue. Eppure, se l'applicazione costruisce le query al database in modo scorretto, questi punti possono diventare la porta d'ingresso per una delle vulnerabilità più conosciute della sicurezza informatica: la SQL injection.
Il problema si verifica quando dati controllati dall'utente vengono interpretati come parte del comando SQL anziché essere trattati esclusivamente come valori.
Le conseguenze possono essere serie: accesso non autorizzato a informazioni, modifica o cancellazione di dati e, in alcune condizioni, ulteriori compromissioni dell'applicazione.
Vediamo come funziona una SQL injection, quali errori la rendono possibile e quali misure adottare per prevenirla, soprattutto nelle applicazioni PHP e MariaDB.
Che cos'è una SQL injection?
SQL è il linguaggio utilizzato da molti database relazionali per interrogare e modificare le informazioni.
Un'applicazione può utilizzare SQL per verificare le credenziali di un utente, recuperare prodotti, mostrare ordini o aggiornare le impostazioni di un account.
Una SQL injection si verifica quando l'applicazione inserisce dati non attendibili all'interno di una query in modo da modificarne la struttura o la logica.
Il problema non è che un utente possa digitare caratteri speciali. Il problema è che il software non separa correttamente i dati dal comando che il database deve eseguire.
Un esempio semplice
Immaginiamo una pagina PHP che cerca un prodotto per nome. Un'implementazione poco sicura potrebbe costruire la query concatenando direttamente il valore ricevuto:
<?php
$nome = $_GET['nome'];
$sql = "SELECT * FROM prodotti WHERE nome = '$nome'";
In questo esempio, il valore proveniente dalla richiesta viene inserito direttamente nella stringa SQL.
Se il dato contiene caratteri che modificano la sintassi della query, il risultato potrebbe non corrispondere più all'operazione prevista dallo sviluppatore.
Non è necessario che un campo contenga codice PHP o JavaScript: la vulnerabilità riguarda il modo in cui il database interpreta la query.
La regola fondamentale è semplice: i dati forniti dall'utente non devono poter modificare la struttura della query SQL.
Che cosa può provocare una SQL injection?
L'impatto dipende dai privilegi dell'account utilizzato dall'applicazione, dalla struttura del database e dal tipo di query vulnerabile.
1. Lettura di informazioni riservate
Una query vulnerabile potrebbe consentire di recuperare dati che normalmente dovrebbero essere accessibili soltanto attraverso determinate funzioni dell'applicazione.
In base al contesto, potrebbero essere coinvolti profili, ordini, informazioni commerciali o altri dati personali.
2. Aggiramento dei controlli di accesso
Se una query viene utilizzata per verificare le credenziali o determinare l'identità di un utente, un errore nella sua costruzione potrebbe alterare la logica del controllo.
L'autenticazione non dovrebbe mai dipendere da query costruite concatenando direttamente input non attendibili.
3. Modifica o cancellazione dei dati
In determinate condizioni, una vulnerabilità può consentire operazioni di scrittura non previste.
Il rischio è maggiore quando l'account del database dispone di permessi eccessivi o quando l'applicazione esegue query che modificano informazioni importanti.
4. Esposizione di dettagli interni
Gli errori del database restituiti direttamente al client possono rivelare nomi di tabelle, colonne, percorsi o informazioni sulla tecnologia utilizzata.
Questi dettagli non sono necessariamente sufficienti per compromettere un sistema, ma possono facilitare l'analisi di una vulnerabilità.
5. Ulteriori conseguenze sull'infrastruttura
In alcune configurazioni, determinate funzionalità del database possono interagire con il filesystem o con altre risorse del sistema. Questi scenari dipendono strettamente dal motore utilizzato, dai privilegi concessi e dalla configurazione.
Non è corretto presumere che ogni SQL injection consenta di eseguire comandi sul server: l'impatto deve essere valutato nel contesto specifico.
Come prevenire la SQL injection in PHP
La difesa principale consiste nell'utilizzare query parametrizzate, che separano la struttura SQL dai valori forniti dall'utente.
Con PDO, per esempio, è possibile scrivere:
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
$dbUser,
$dbPassword,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
$stmt = $pdo->prepare(
'SELECT id, nome, prezzo
FROM prodotti
WHERE nome = :nome'
);
$stmt->execute([
'nome' => $_GET['nome'] ?? '',
]);
$prodotti = $stmt->fetchAll(PDO::FETCH_ASSOC);
In questo esempio, il valore di nome viene passato come parametro e non inserito direttamente nella struttura della query.
Le query preparate sono una difesa fondamentale contro la SQL injection nei valori, ma non risolvono automaticamente ogni problema legato alla costruzione dinamica delle query.
È inoltre importante gestire correttamente gli errori, verificare gli input secondo le esigenze dell'applicazione e assegnare al database soltanto i privilegi necessari.
Attenzione ai nomi di colonne e alle parti dinamiche
I parametri SQL sono progettati per rappresentare valori, non per sostituire liberamente nomi di tabelle, colonne o parole chiave.
Immaginiamo che una pagina permetta di ordinare i risultati per prezzo o nome. Non è corretto inserire direttamente un parametro ricevuto dal client nella posizione del nome della colonna.
La soluzione consiste nel definire una lista chiusa di opzioni consentite e utilizzare esclusivamente i valori previsti dal codice.
Per esempio:
<?php
$ordinamenti = [
'nome' => 'nome',
'prezzo' => 'prezzo',
];
$richiesto = $_GET['ordine'] ?? 'nome';
$colonna = $ordinamenti[$richiesto] ?? 'nome';
$sql = "SELECT id, nome, prezzo
FROM prodotti
ORDER BY $colonna";
In questo caso, il valore interpolato proviene da una lista definita dall'applicazione e non direttamente dall'input dell'utente.
La stessa attenzione è necessaria per filtri dinamici, nomi di colonne, direzioni di ordinamento e altre parti della query che non possono essere parametrizzate come normali valori.
Perché l'escaping da solo non è la soluzione migliore
Una pratica storica consiste nell'utilizzare funzioni di escaping per modificare i caratteri speciali prima di inserirli nelle query.
L'escaping può avere un ruolo in alcuni contesti specifici, ma è facile applicarlo in modo incompleto o con regole incompatibili con la connessione al database.
Per questo motivo, quando sono disponibili, le query parametrizzate rappresentano normalmente la scelta preferibile.
È altrettanto importante evitare di considerare la validazione dell'input una sostituzione delle query preparate. Un campo che accetta soltanto numeri può comunque essere gestito in modo scorretto altrove nell'applicazione.
La strategia corretta combina query parametrizzate, validazione coerente con il dato atteso e gestione prudente degli errori.
Il principio del minimo privilegio nel database
Anche un'applicazione ben sviluppata può contenere bug. Per questo è utile ridurre i danni potenziali nel caso in cui una vulnerabilità venga sfruttata.
L'account utilizzato dall'applicazione dovrebbe avere soltanto i permessi necessari alle sue funzioni.
Per esempio, un servizio che deve leggere un catalogo non dovrebbe necessariamente poter modificare ogni tabella del database o gestire gli utenti del database stesso.
Separare i privilegi per attività differenti, quando possibile, limita le conseguenze di un errore applicativo.
È inoltre opportuno evitare di utilizzare account amministrativi del database per le normali richieste del sito.
Come verificare la sicurezza di un'applicazione
Una revisione efficace dovrebbe includere tutti i punti in cui dati esterni vengono utilizzati per costruire query:
- [ ] Form di login e registrazione.
- [ ] Campi di ricerca e filtri.
- [ ] Parametri negli URL.
- [ ] Endpoint delle API.
- [ ] Funzioni di ordinamento e paginazione.
- [ ] Importazioni di file e processi automatici.
- [ ] Query utilizzate nei pannelli amministrativi.
- [ ] Componenti e plugin di terze parti.
Durante i test autorizzati, è importante utilizzare un ambiente controllato e verificare che input inattesi non alterino la logica delle query.
Anche i log e i messaggi di errore meritano attenzione: i dettagli tecnici dovrebbero essere registrati in modo sicuro per chi gestisce il sistema, senza essere esposti inutilmente agli utenti.
Gli strumenti automatici possono aiutare a individuare alcuni segnali, ma non sostituiscono la revisione del codice e la verifica dei comportamenti applicativi.
SQL injection e siti WordPress
Anche WordPress e i suoi plugin utilizzano database. Un sito può quindi essere esposto a SQL injection quando un tema o un plugin costruisce query in modo non sicuro.
Gli sviluppatori che utilizzano $wpdb dovrebbero preferire i meccanismi di preparazione delle query previsti da WordPress, come $wpdb->prepare(), applicandoli correttamente ai valori.
È importante ricordare che la sicurezza di un sito non dipende soltanto dal core di WordPress: codice personalizzato, plugin e temi devono essere mantenuti e verificati con la stessa attenzione.
Aggiornare i componenti riduce l'esposizione a vulnerabilità note, ma non corregge automaticamente errori presenti nel codice personalizzato.
Conclusione
La SQL injection è una vulnerabilità prevenibile nella maggior parte dei casi attraverso una corretta progettazione delle query, la separazione tra codice e dati e l'uso di privilegi limitati per il database.
Con MiHakero puoi analizzare domini, applicazioni web, API e servizi pubblici per individuare esposizioni e problemi rilevabili dall'esterno. Per confermare o escludere una SQL injection è spesso necessario integrare l'analisi esterna con test applicativi autorizzati e una revisione del codice che interagisce con il database.