La SQL Injection è una vulnerabilità che può verificarsi quando un'applicazione web costruisce ed esegue query SQL utilizzando dati forniti dall'utente senza applicare adeguate tecniche di validazione e parametrizzazione. Il problema non riguarda il database in sé, ma il modo in cui il software costruisce le istruzioni SQL prima di inviarle al database.

Quando un'applicazione concatena direttamente un valore proveniente da una richiesta HTTP all'interno di una query, l'input dell'utente può diventare parte della sintassi SQL. In determinate condizioni, questo permette di modificare il significato della query originale e di ottenere comportamenti non previsti dallo sviluppatore.

Il rischio può essere particolarmente elevato nelle applicazioni che gestiscono account, clienti, ordini, documenti, dati finanziari o informazioni riservate, perché una vulnerabilità SQL Injection può trasformarsi in accesso non autorizzato ai dati presenti nel database.

Come nasce il problema

Per comprendere la SQL Injection bisogna partire dal modo in cui un'applicazione comunica con il database.

Un'applicazione PHP, ad esempio, può ricevere un parametro attraverso una richiesta HTTP e utilizzarlo per effettuare una ricerca. Se il valore ricevuto viene concatenato direttamente alla query, il database non può distinguere in modo affidabile tra la parte di input dell'utente e la struttura dell'istruzione SQL.

Il problema nasce quindi quando dati e codice SQL vengono trattati come un'unica stringa.

Una query costruita dinamicamente dovrebbe invece mantenere separati il comando SQL e i valori che devono essere inseriti all'interno dell'operazione.

Perché concatenare le variabili è rischioso

Il problema può essere rappresentato da una situazione molto comune nello sviluppo PHP.

Un'applicazione potrebbe costruire una query attraverso una concatenazione di stringhe, inserendo direttamente un parametro ricevuto dall'utente. Se quel parametro contiene caratteri che hanno un significato speciale per il linguaggio SQL, il risultato finale potrebbe non essere più quello previsto dallo sviluppatore.

Il database esegue infatti la query risultante, non conosce l'intenzione originale del programmatore.

Questo significa che una semplice ricerca, autenticazione o selezione di un record può diventare un punto di ingresso per manipolare la logica dell'istruzione SQL.

SQL Injection e autenticazione

Uno degli scenari storicamente più conosciuti riguarda i form di login.

Un'applicazione potrebbe cercare nel database un utente confrontando username e password. Se i valori vengono inseriti direttamente nella query, un input appositamente costruito può tentare di alterare la condizione utilizzata per verificare le credenziali.

Il problema è particolarmente grave perché l'attaccante non deve necessariamente conoscere la password reale. Se riesce a modificare la logica della query, potrebbe tentare di ottenere un risultato differente da quello previsto.

Un sistema di autenticazione moderno deve quindi utilizzare query parametrizzate, gestione corretta delle password e controlli applicativi indipendenti dalla semplice costruzione della query SQL.

SQL Injection e lettura dei dati

Una SQL Injection non deve essere associata solamente al login.

Se una vulnerabilità viene individuata in una funzione di ricerca o consultazione, l'attaccante potrebbe tentare di modificare la query per ottenere informazioni aggiuntive rispetto a quelle previste dall'applicazione.

Il livello di esposizione dipende dai privilegi dell'utente database utilizzato dall'applicazione e dalla struttura delle query.

Se l'account utilizzato dall'applicazione dispone di permessi eccessivi, una vulnerabilità può avere conseguenze molto più estese.

SQL Injection e modifica dei dati

In determinate condizioni una vulnerabilità può consentire non solamente di leggere informazioni, ma anche di modificare o eliminare dati.

Il rischio dipende dai comandi SQL che l'applicazione può eseguire e dai privilegi assegnati all'utente database.

Per questo motivo la sicurezza contro SQL Injection deve essere affrontata su più livelli: codice applicativo, gestione delle query, permessi del database e configurazione dell'infrastruttura.

Prepared Statement: la protezione principale

La tecnica più importante per prevenire SQL Injection è utilizzare prepared statement, cioè query parametrizzate.

Con PHP e PDO, invece di costruire una query concatenando direttamente i valori, si definisce la struttura SQL separatamente dai parametri.

Un esempio corretto può essere:

$sql = "SELECT id, nome, email FROM clienti WHERE id = :id";

$stmt = $pdo->prepare($sql);
$stmt->execute([
':id' => $id
]);

In questo caso :id rappresenta un parametro e il valore viene fornito separatamente alla query.

Il database può quindi distinguere la struttura dell'istruzione dal contenuto del parametro, impedendo che il valore fornito dall'utente venga interpretato come parte della sintassi SQL.

Perché filtrare l'input non è sufficiente

La validazione degli input è importante, ma non dovrebbe essere considerata una sostituzione delle query parametrizzate.

Controllare che un ID sia numerico, verificare la lunghezza di una stringa o limitare determinati caratteri può ridurre alcuni rischi, ma non rappresenta una protezione generale contro SQL Injection.

La difesa principale deve essere la separazione tra codice SQL e dati attraverso prepared statement.

La validazione rimane comunque fondamentale per verificare che i dati ricevuti abbiano il formato previsto dall'applicazione.

Se un parametro deve essere un intero, ad esempio, l'applicazione dovrebbe trattarlo come tale invece di accettare arbitrariamente qualsiasi stringa.

Attenzione anche a ORDER BY e nomi delle colonne

Esiste un aspetto tecnico importante quando si lavora con query dinamiche. I prepared statement sono estremamente efficaci per i valori, ma non possono essere utilizzati indiscriminatamente per sostituire nomi di tabelle, nomi di colonne o parti strutturali della query.

Un esempio frequente riguarda le pagine che permettono all'utente di scegliere il campo di ordinamento.

Invece di inserire direttamente il parametro ricevuto nella query, è preferibile utilizzare una whitelist interna.

L'applicazione può quindi associare un valore controllato dall'utente a un insieme limitato di colonne consentite.

Questo principio è importante perché la sicurezza deve essere applicata anche alle parti dinamiche della query che non possono essere trattate come semplici parametri.

Il ruolo della validazione

La validazione serve a verificare che un dato abbia effettivamente il formato previsto.

Un ID cliente può essere un intero, una data deve rispettare un determinato formato, mentre un campo email deve rispettare le caratteristiche previste dall'applicazione.

La validazione dovrebbe essere effettuata lato server, perché i controlli eseguiti esclusivamente tramite JavaScript possono essere facilmente aggirati.

Il browser deve quindi essere considerato un ambiente non attendibile e tutti i dati ricevuti dal client devono essere trattati come input potenzialmente manipolabile.

Gestire correttamente i privilegi del database

Anche se l'applicazione utilizza query parametrizzate, è buona pratica applicare il principio del least privilege all'account utilizzato per accedere al database.

Se un'applicazione deve solamente leggere determinati dati, il relativo account non dovrebbe necessariamente avere permessi di modifica o eliminazione su tutte le tabelle.

Ridurre i privilegi limita il possibile impatto di una vulnerabilità.

Se un attaccante riuscisse infatti a sfruttare un errore applicativo, avrebbe a disposizione solamente le operazioni consentite all'utente database compromesso.

Errori SQL e informazioni esposte

Un altro aspetto spesso sottovalutato riguarda gli errori restituiti dal database.

Durante lo sviluppo può essere utile visualizzare dettagli come query, nomi delle tabelle, colonne e messaggi di errore. In produzione, invece, mostrare queste informazioni direttamente all'utente può fornire dati utili a chi sta tentando di individuare vulnerabilità.

Un'applicazione professionale dovrebbe registrare gli errori nei log lato server e mostrare all'utente un messaggio generico, senza esporre dettagli interni dell'implementazione.

In questo modo è possibile mantenere le informazioni utili alla diagnostica senza trasformarle in una fonte di informazioni per un attaccante.

SQL Injection nelle applicazioni PHP

Le applicazioni PHP e MySQL sono un esempio particolarmente comune quando si parla di SQL Injection, soprattutto nei progetti che utilizzano query costruite dinamicamente.

L'utilizzo di PDO permette di adottare prepared statement e una gestione strutturata delle connessioni al database.

È importante però ricordare che utilizzare PDO non rende automaticamente sicura un'applicazione. Anche con PDO è possibile scrivere codice vulnerabile se le query vengono costruite concatenando direttamente input non controllati.

La sicurezza deriva quindi dal modo in cui vengono utilizzate le funzionalità disponibili.

SQL Injection e ORM

Anche applicazioni che utilizzano framework o ORM non sono automaticamente immuni.

Molti strumenti forniscono meccanismi di parametrizzazione e query builder che riducono il rischio, ma è comunque possibile introdurre vulnerabilità quando si utilizzano query SQL raw o si costruiscono dinamicamente parti dell'istruzione.

Lo sviluppatore deve quindi comprendere cosa avviene realmente tra applicazione e database, anche quando il framework nasconde parte della complessità.

Come verificare un'applicazione

Durante un'attività di sicurezza è possibile analizzare le funzionalità che ricevono input dall'utente e verificare come questi valori vengono utilizzati nelle query.

Particolare attenzione dovrebbe essere rivolta a login, ricerca, filtri, paginazione, parametri GET e POST, API, form amministrativi e URL contenenti identificativi.

L'obiettivo di un test professionale non è semplicemente verificare se un'applicazione "accetta caratteri strani", ma capire se l'input riesce effettivamente a modificare la logica della query e quali privilegi sarebbero disponibili in caso di compromissione.

Monitoraggio e logging

La prevenzione deve essere accompagnata dal monitoraggio.

Log applicativi, errori database, richieste anomale e tentativi ripetuti di accesso possono fornire informazioni importanti durante un'indagine.

Un sistema di monitoraggio ben configurato permette di correlare eventi provenienti dal web server, dall'applicazione e dal database.

Questo può aiutare a individuare comportamenti anomali prima che una vulnerabilità venga sfruttata con successo.

La sicurezza SQL parte dal codice

Prevenire una SQL Injection non significa semplicemente aggiungere un filtro ai campi di un form. È necessario progettare correttamente il rapporto tra applicazione e database, mantenendo separati codice SQL e dati, validando gli input, limitando i privilegi e gestendo correttamente gli errori.

Per uno sviluppatore PHP che utilizza MySQL, l'impiego sistematico di PDO con prepared statement rappresenta uno dei passaggi fondamentali per costruire applicazioni più sicure.

In ALLIT, quando viene sviluppata o analizzata un'applicazione web, la sicurezza del database deve essere considerata parte integrante dell'architettura software. Query parametrizzate, gestione corretta degli accessi, validazione lato server e controllo dei privilegi permettono di ridurre significativamente il rischio di SQL Injection e di proteggere i dati gestiti dall'applicazione.

La SQL Injection dimostra infatti un principio fondamentale della sicurezza informatica: un dato proveniente dall'utente non deve mai essere trattato come codice. Quando questa separazione viene mantenuta correttamente, il database può interpretare l'input come semplice valore e non come una possibile modifica della logica dell'applicazione.