Cos'è SQL Injection e Come Lo Previeni?
Un'analisi pratica degli attacchi SQL injection, esempi reali e le correzioni concrete che effettivamente li fermano nel codice di produzione.
SQL injection esiste dalla fine degli anni Novanta e rimane uno dei modi più comuni in cui le applicazioni web vengono compromesse. Il bug è semplice: un attacker invia input che viene interpretato come codice SQL invece di dati normali, e il database fa qualcosa che non avrebbe mai dovuto fare.
Come funziona effettivamente l'attacco
Immagina un form di login che costruisce una query così in PHP:
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
Se l'app non bonifica l'input, un attacker digita admin' -- nel campo username. La query diventa:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
Il -- commenta il resto della riga, quindi il controllo della password non avviene mai. È un classico auth bypass. Gli attacker usano anche UNION SELECT per estrarre dati da altre tabelle, o mettono in stack query con un punto e virgola per eseguire qualcosa di distruttivo come DROP TABLE users; se il driver consente più statement.
C'è anche blind SQL injection, dove l'app non mostra direttamente i risultati della query. Gli attacker deducono le informazioni tramite timing (SLEEP(5) in MySQL) o risposte booleane (la pagina si comporta diversamente per una condizione vera vs. falsa). Strumenti come sqlmap automatizzano questo tipo di estrazione una volta trovato un parametro vulnerabile.
Perché la concatenazione di stringhe è il problema di fondo
Ogni variante di questo attacco torna a una cosa: mescolare codice e dati nella stessa stringa. Il database non riesce a distinguere tra un valore legittimo e una sintassi iniettata perché arrivano nello stesso canale. L'escaping delle virgolette aiuta in alcuni casi ma è fragile — encodings diversi, second-order injection (dati memorizzati una volta, poi riutilizzati in modo non sicuro dopo), e le stranezze specifiche del driver creano tutti percorsi di bypass. L'escaping è una patch, non una correzione.
Le query parametrizzate sono la vera correzione
La correzione è separare il codice SQL dai dati dell'utente a livello di driver, usando query parametrizzate (chiamate anche prepared statements). Il database riceve prima la struttura della query, poi collega i valori dopo, quindi l'input dell'utente non può mai cambiare il significato della query.
In Python con psycopg2:
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
In Node.js con mysql2:
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
In Java con JDBC:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
Nota lo schema: i placeholder (%s, ?) tengono il posto per i dati, e i valori effettivi vengono passati separatamente. Nessuna concatenazione di stringhe, nessun escaping manuale necessario. Funziona per la stragrande maggioranza delle query che scriverai in un'applicazione normale.
Cosa succede con nomi di tabelle o colonne dinamici?
La parametrizzazione gestisce i valori ma non gli identificatori — non puoi collegare un nome di tabella come parametro. Se la tua app ha bisogno di selezionare una tabella dinamicamente (raro, e spesso un cattivo design), inserisci in una whitelist i valori consentiti rispetto a un elenco hardcoded invece di fidarti direttamente dell'input dell'utente:
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
Non costruire mai nomi di identificatori tramite string formatting dall'input dell'utente, nemmeno con escaping.
Difese stratificate oltre la query stessa
Le query parametrizzate sono il controllo primario, ma poche altre cose contano:
- Least privilege sull'account del database. L'utente DB dell'app non dovrebbe avere
DROP,ALTER, o accesso a schemi non correlati. Se un injection comunque passa attraverso, i privilegi limitati limitano il danno. - Gli ORM aiutano per impostazione predefinita. Django ORM, SQLAlchemy, e Hibernate parametrizzano automaticamente le query quando usi i loro metodi standard di costruzione delle query. Il rischio riappare quando gli sviluppatori passano a raw SQL o usano chiamate
.extra()/text()con interpolazione di stringhe — quindi controlla specificamente quei punti. - La validazione dell'input è un livello secondario, non un sostituto. Controllare che un campo email sembri un'email è una buona pratica, ma non ferma l'injection da sola — gli attacker trovano payload creativi che passano comunque la validazione lax.
- Un WAF può catturare schemi di attacco noti, ma è un livello di rilevamento, non una correzione per il codice sottostante.
Testare il tuo codice per questo bug
Esegui le tue query attraverso uno strumento di analisi statica (Bandit per Python, Semgrep con rulesets SQL injection) come parte della CI. Per il test manuale, prova a iniettare una virgoletta singola (') in ogni campo di input e guarda se compaiono messaggi di errore SQL nella risposta — spesso è il primo segno che una query non è parametrizzata.
Se vuoi approfondire, il track Web Security di Korra Studio copre injection insieme a XSS e auth bypass, e i segmenti Databases illustrano pattern di design delle query che evitano interamente questa classe di bug.
Scritto con assistenza AI, revisionato e pubblicato da Michal Pilch (CISSP), Korra Studio.
Questa è una nota dalla knowledge base di Korra Studio — la piattaforma abbina ogni argomento a mentoring 1-to-1.
Inizia gratisarrow_forward