Nel grande teatro dell’informatica, dove ogni utente è un attore e ogni dato una battuta, c’è un ruolo che spesso viene sottovalutato: quello del malintenzionato con troppo tempo libero e qualche conoscenza di SQL, ovvero uno dei protagonisti silenziosi e più longevi delle notti insonni degli sviluppatori.
Cos’è la SQL Injection?
L’SQL injection è una tecnica usata dagli hacker per ingannare un’applicazione e farle eseguire comandi dannosi sul suo database. Funziona quando un sito o un’app accetta dei dati inseriti dall’utente (come un nome o una password) e li usa direttamente per costruire una richiesta al database, senza controllarli prima. Se il sistema non è protetto, un attaccante può inserire del codice “finto” al posto dei dati veri, e questo codice viene eseguito come se fosse un’istruzione legittima. In pratica, è come scrivere una frase che il sistema prende alla lettera… anche se dice qualcosa di pericoloso. Con una SQL injection, un malintenzionato può visualizzare dati riservati, modificarli o persino cancellarli del tutto. È uno degli attacchi più comuni e pericolosi quando si parla di sicurezza informatica.
Come funziona
Immaginiamo un’applicazione web con un modulo di login in cui l’utente inserisce nome utente e password. Se il codice che gestisce questi dati inserisce i valori direttamente nella query SQL, il database potrebbe essere indotto ad eseguire comandi non previsti.
Esempio di query vulnerabile:
SELECT * FROM users WHERE username = 'admin' AND password = '123456';Un utente malintenzionato può inserire al posto del nome utente:
' OR '1'='1Portando la query a diventare:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...';E indovina un po’? '1'='1' è sempre vero. Quindi il database esegue la query come se l’utente fosse autenticato. Voilà: accesso garantito senza sapere nulla del sistema, neanche il nome dell’amministratore. Un piccolo trucco semantico, e il castello cade.
👉 Questo attacco non è hacking da film con schermi verdi e codice che scorre a 1000 righe al secondo. È più simile a una truffa ben scritta su carta intestata.
Esempio pratico: codice PHP vulnerabile
Immaginiamo un programmatore (con un po’ troppa fretta e forse poca caffeina) che scrive questo codice PHP per gestire un login:
<?php
$conn = new mysqli("localhost", "user", "password", "testdb");
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $conn->query($query);
if ($result->num_rows > 0) {
echo "Accesso consentito";
} else {
echo "Accesso negato";
}
?>Sembra innocuo, vero? Ma qui c’è un problema grande quanto un portone: i dati inseriti dall’utente vengono infilati direttamente nella query SQL, senza il minimo controllo.
Versione sicura con prepared statements
Ma il nostro programmatore, dopo un espresso e una lettura su OWASP, capisce l’errore e riscrive il tutto, correttamente, come segue:
<?php
$conn = new mysqli("localhost", "user", "password", "testdb");
$username = $_POST['username'];
$password = $_POST['password'];
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
echo "Accesso consentito";
} else {
echo "Accesso negato";
}
?>Con le prepared statements, i dati dell’utente non vengono inseriti direttamente nella query, ma passati come parametri. Questo permette al database di distinguere tra codice e contenuto, evitando che un input malevolo venga interpretato come parte della query.
In pratica: è come parlare con qualcuno attraverso un interprete fidato, che si assicura che le tue parole non vengano travisate per attaccare chi ascolta.
Simulazione in ambiente controllato
No, non ti sto suggerendo di testarlo su siti del governo (che tra l’altro sono spesso già sotto stress). Ti consiglio invece ambienti controllati, sicuri e progettati proprio per imparare:
🔹 DVWA – Damn Vulnerable Web Application
Un classico intramontabile per chi studia sicurezza web. Lo installi in locale e ti diverti a bucare (eticamente!) ogni funzione.
📌 GitHub: https://github.com/digininja/DVWA
🔹 bWAPP – Buggy Web Application
É come un’arnia piena di bug. Ma stavolta è voluto. Ti mette a disposizione decine di vulnerabilità da esplorare, SQLi inclusa.
📌 SourceForge: https://sourceforge.net/projects/bwapp/
💡 Usali in ambienti isolati o virtuali. Il tuo computer di tutti i giorni non ha bisogno di diventare un parco giochi per malware.
Entrambi permettono di simulare exploit senza rischi legali o tecnici.
Rilevamento: come scoprire vulnerabilità SQLi
Manualmente:
- Inserendo payload comuni nei form (
' OR 1=1 --,admin' --) - Analizzando risposte anomale del server
Strumenti automatici:
- sqlmap: strumento open source per identificare e sfruttare vulnerabilità SQLi
sqlmap -u “http://localhost/test.php” –data=”username=admin&password=123″ - Burp Suite (edizione Community e Professional), un proxy con tanto di scanner automatico ed edizione gratuita disponibile.
- OWASP ZAP, open source e perfetto per iniziare con l’analisi automatica del traffico web.
E le API? Anche lì può nascondersi la SQL Injection
Molti pensano che la SQL Injection riguardi solo i vecchi moduli HTML con i campi username e password.
Spoiler: anche le API moderne possono essere vulnerabili.
Immagina un’app che non ha un form visibile, ma invia i dati in background tramite JSON (come fanno la maggior parte delle app web moderne o mobile).
Per esempio, quando clicchi su “Accedi” in un’app, può partire una richiesta tipo questa:
POST /api/user/login
Content-Type: application/json
{
"username": "admin",
"password": "password123"
}Fin qui tutto bene. Ma se il codice sul server inserisce direttamente questi valori in una query SQL, senza controllarli, succede la stessa identica cosa del classico form vulnerabile:
SELECT * FROM users WHERE username = 'admin' AND password = 'password123';Un attaccante può inviare invece:
{
"username": "' OR '1'='1",
"password": "qualunque"
}Che porta a questa query:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'qualunque';E taaaaaac: accesso garantito anche via API.
Ricorda che esistono anche varianti più silenziose come la Blind SQLi o la Time-based SQLi, usate quando il sistema non restituisce direttamente gli errori.
➡️ Moralina finale: non importa se i dati arrivano da un form HTML, da un’app mobile o da un endpoint REST in JSON —se i dati dell’utente finiscono in una query SQL senza controllo, sei vulnerabile, punto.
Difesa: come prevenire la SQL injection
- Prepared Statements (query parametrizzate)
Evitano la concatenazione diretta dei dati, sono il santo graal della sicurezza SQL. Separano i dati dal codice, come si deve fare con gli oggetti contundenti in aereo.
$stmt->bind_param(“ss”, $username, $password);
$stmt->execute(); - Filtraggio e validazione dell’input
Non fidarti mai dell’utente. Neanche se ti dice di essere tua madre.
Usa whitelisting dove possibile. - Limitazione dei privilegi del database
Non dare al tuo utente MySQL il potere di cancellare l’intero schema. Dagli solo l’essenziale, come si fa con un neopatentato e una Panda del ‘97.
L’utente DB dell’applicazione deve avere accesso minimo! - Disattivare messaggi di errore in produzione
I dettagli degli errori SQL non sono consigli per lo sviluppatore, sono spoiler per l’attaccante. - Monitoraggio e logging
Traccia i tentativi sospetti. Una query troppo creativa può nascondere cattive intenzioni.
I WAF possono mitigare l’impatto delle SQLi, ma non sostituiscono una scrittura del codice sicura.
Un Web Application Firewall analizza il traffico in entrata e può bloccare input sospetti prima che raggiungano l’applicazione.
Ma se il codice accetta comunque quell’input (magari perché il WAF non lo ha riconosciuto), la vulnerabilità resta.
È una difesa utile, ma esterna — non una scusa per scrivere codice insicuro.
Proteggere le API REST dalla SQL Injection: esempi concreti
Che tu stia costruendo una semplice API in Flask o una mega architettura in Node.js, le regole non cambiano:
Mai fidarsi dell’input utente.
Sempre usare query parametrizzate o ORM sicuri.
Mai concatenare stringhe direttamente nelle query SQL.
Ecco come si fa nel modo giusto, nei tre ambienti più usati:
Node.js + Express + MySQL
❌ Codice vulnerabile:
const express = require('express');
const app = express();
const mysql = require('mysql2');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
const db = mysql.createConnection({ /* config */ });
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query, (err, results) => {
if (results.length > 0) res.send("Accesso consentito");
else res.send("Accesso negato");
});
});✅ Versione sicura con query parametrizzata:
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
const query = `SELECT * FROM users WHERE username = ? AND password = ?`;
db.query(query, [username, password], (err, results) => {
if (results.length > 0) res.send("Accesso consentito");
else res.send("Accesso negato");
});
});Python + Flask + SQLite
❌ Codice vulnerabile:
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
query = f"SELECT * FROM users WHERE username = '{data['username']}' AND password = '{data['password']}'"
cursor.execute(query)
# ...✅ Versione sicura con query parametrizzata:
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (data['username'], data['password']))
# ...PHP + PDO
✅ Esempio sicuro (PDO è molto più sicuro di mysqli se usato correttamente):
<?php
$dbh = new PDO('mysql:host=localhost;dbname=testdb', $user, $pass);
$stmt = $dbh->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->bindParam(':username', $_POST['username']);
$stmt->bindParam(':password', $_POST['password']);
$stmt->execute();
$result = $stmt->fetchAll();
?>Proteggere le API non significa rendere il codice complicato, ma solo cambiare come interagisci con i dati.
Evita la concatenazione come eviteresti un tunnel buio in un horror.
Usa parametri come usi una cintura in auto: non costa nulla, ma ti salva la vita.
Strumenti utili
| Strumento | Scopo | Link |
|---|---|---|
| DVWA | Test didattico web | GitHub |
| bWAPP | Simulazione vulnerabilità | SourceForge |
| sqlmap | Test automatici SQLi | sqlmap.org |
| Burp Suite | Analisi e pentesting web | portswigger.net |
| OWASP ZAP | Scanner gratuito | owasp.org |
Falsi miti sulla SQL Injection
Nel mondo dello sviluppo, ci sono alcune frasi che si sentono spesso nei corridoi virtuali di GitHub, Stack Overflow e Slack…
Ecco i grandi classici che sembrano tranquillizzanti, ma in realtà sono solo mezze verità.
❌ “Uso un ORM, quindi sono al sicuro!”
✅ Dipende da come lo usi.
Gli ORM (Object-Relational Mapper) aiutano a scrivere meno SQL manuale, ma non sono magici.
Se usi metodi sicuri (come i repository o il query builder) va benissimo.
Ma se ti lanci in createQuery() con concatenazioni come questa:
$qb = $em->createQuery("SELECT u FROM User u WHERE u.name = '$name'");…hai appena ricreato una SQL Injection, solo con più passaggi e un lessico più elegante.
❌ “Accetto solo richieste JSON, quindi sono protetto”
✅ Ma il JSON può portare dati tossici come chiunque altro.
Il payload cambia formato, non intenzione.
Ricevere un oggetto {"username": "' OR 1=1 --"} è esattamente come riceverlo da un form HTML.
❌ “Tanto nessuno conosce il mio schema del database”
✅ Famoso ultimo pensiero prima del breach.
Sicurezza per oscurità non è sicurezza. Un attaccante bravo (o anche solo fortunato con un UNION SELECT) può dedurre molte informazioni dal comportamento dell’app, dagli errori o da un messaggio troppo loquace.
❌ “Validare l’input con JavaScript è sufficiente”
✅ Solo se l’attaccante è un gentleman che rispetta il tuo front-end.
JavaScript gira lato client.
Un attaccante serio invia la richiesta direttamente all’API, bypassando il tuo onsubmit.
❌ “Ho disabilitato i messaggi di errore, quindi nessuno saprà se sbaglia”
✅ Meglio, ma non basta.
I messaggi di errore sono una spia utile… ma non sono l’unica via per un attaccante.
Un comportamento sospetto (es. login riuscito con input anomalo) è già un segnale di vulnerabilità.
❌ “SQLi? Quella roba era per i siti anni 2000…”
✅ Ed è ancora qui, viva e vegeta nel 2025.
Come il vinile e il fax nei comuni, la SQL Injection non passa mai di moda.
Perché il codice nuovo può contenere errori vecchi. Basta un copy-paste sbagliato, un bind dimenticato, un “faccio prima così”.
🧘 Morale della favola: La vera sicurezza nasce non da ciò che usi, ma da come pensi.
Un dev consapevole vale più di mille plugin.
Approfondimento: perché la SQL Injection continua a esistere nel 2025?
“Errare è umano, ma concatenare l’input utente in una query SQL è diabolico.”
A questo punto potresti chiederti:
“Ma com’è possibile che nel 2025 stiamo ancora parlando di SQL Injection, una vulnerabilità vecchia di decenni?”
La risposta breve è: perché scrivere codice sicuro è più difficile di quanto sembri. La risposta lunga, invece, merita qualche riflessione in più.
1. La falsa fiducia negli strumenti
Molti sviluppatori si affidano ciecamente ai framework, ORM, CMS o “librerie magiche” credendo che gestiscano tutto in automatico. Spoiler: non sempre lo fanno, o non nel modo che pensi. Un Entity Manager può ancora essere manipolato male. E sì, puoi iniettare anche in un’API REST se ti impegni.
2. La mancanza di cultura sulla sicurezza
Spesso la sicurezza è vista come una fase opzionale, da affrontare “dopo che tutto funziona”. Ma se “funziona” significa che chiunque può loggarsi scrivendo ' OR 1=1 --, forse è il caso di rivedere le priorità.
3. La pressione sui tempi di sviluppo
Molti progetti vengono sviluppati in fretta. Scadenze strette, clienti impazienti, “intelligenze artificiali” che ti scrivono codice su richiesta. Ma anche il miglior prompt non può garantire la sicurezza se il contesto è sbagliato.
4. Applicazioni legacy e codice ereditato
Gran parte dei software ancora in uso è basata su codice scritto 10 o 15 anni fa, spesso da sviluppatori che ormai fanno tutt’altro nella vita. E modificare quel codice è rischioso, costoso o semplicemente ai evita per pigrizia.
5. L’effetto “non succederà a noi”
C’è sempre l’illusione che la sicurezza sia un problema per “i grandi”. Ma spesso le SQLi colpiscono siti piccoli, applicazioni interne o microservizi dimenticati. E sono proprio quelli che nessuno monitora.
Riflessione finale: la sicurezza è un processo, non una libreria
Non esiste una funzione magica che ti protegga da tutto. La sicurezza, come la filosofia, è fatta di domande continue, verifiche costanti e un sano dubbio verso ogni input esterno. È un’abitudine da coltivare ogni giorno nel ciclo di sviluppo, non un plugin da attivare all’ultimo deploy.
La SQL Injection è una vulnerabilità tanto semplice quanto devastante. Anche se conosciuta da decenni, è ancora sfruttata con successo a causa di errori banali nella scrittura del codice. Tuttavia, con un minimo di attenzione, buone pratiche di sviluppo e strumenti adeguati, è possibile evitarla del tutto.
Il problema non è la tecnologia, ma come viene usata — o meglio, abusata.
Scrivere codice sicuro è un atto di umiltà: significa riconoscere che l’utente potrebbe voler rompere il gioco, e il tuo compito è impedirglielo con grazia, rigore e qualche controllo in più.
Vuoi una guida passo-passo su come usare sqlmap in un ambiente controllato? Scrivilo nei commenti 👇
Fonti consigliate:
- OWASP: https://owasp.org/www-community/attacks/SQL_Injection
- SQLMap Official: https://sqlmap.org/
- Guida DVWA: https://github.com/digininja/DVWA
- Esempi di Payload reali: PayloadAllTheThings – SQL Injection
⚡ Questo nodo della rete è alimentato da conoscenza libera e caffeina.
Se hai trovato qualcosa di utile, puoi supportarci con un caffè digitale.
👉 Offrimi un caffè
