Hai mai visto un form contatti che invia dati in chiaro a una query SQL scritta a mano? Noi sì. E ogni volta che un client ci affida un sito costruito con query raw senza alcun parametro vincolato, il nostro primo pensiero è: “Qui basta una virgoletta per far cadere tutto il database.”
La SQL Injection è la più classica delle vulnerabilità web, ma è ancora la più subdola perché basta un errore di distrazione. In Laravel — il framework PHP che usiamo quotidianamente per progetti complessi — gli strumenti per prevenirlo sono già pronti. Devi solo saperli usare.
Noi, di Meteora Web, abbiamo visto server esposti e dati rubati per colpa di query non protette. In questa guida ti mostriamo esattamente come evitarlo, con codice che puoi copiare e testare subito.
Perché la SQL Injection funziona ancora
Un attacco SQL Injection si verifica quando l’input utente viene concatenato direttamente in una query. L’attaccante inserisce un carattere di escape (es. una virgoletta singola) e modifica la logica della query.
Esempio vulnerabile:
$email = $_POST['email'];
$query = "SELECT * FROM users WHERE email = '" . $email . "'";
$result = mysqli_query($conn, $query);Se l’utente inserisce admin' --, la query diventa:
SELECT * FROM users WHERE email = 'admin' --'E l’attaccante accede come admin senza conoscere la password.
Laravel elimina questo problema se usi correttamente il suo stack. Ma attenzione: anche in Laravel puoi scrivere codice pericoloso se fai query raw senza parametri.
I tre livelli di protezione in Laravel
1. Query Builder: il metodo più sicuro e veloce
Il Query Builder di Laravel usa prepared statements sotto il cofano. I valori vengono inviati separatamente dalla struttura della query, rendendo impossibile alterare la logica.
$users = DB::table('users')
->where('email', $request->input('email'))
->first();Non importa cosa scrive l’utente: il binding lo rende innocuo. Questo funziona per where, insert, update, delete.
Attenzione a whereRaw e orderByRaw: Se devi usare query grezze, passa sempre i parametri con array associativo.
// SICURO
$users = DB::table('users')
->whereRaw('email = ?', [$request->email])
->get();
// ANCORA PIÙ SICURO con nome parametro
$users = DB::table('users')
->whereRaw('email = :email', ['email' => $request->email])
->get();2. Eloquent ORM: protezione nativa con Attributi Mass Assignable
Eloquent estende la sicurezza grazie al sistema di $fillable e $guarded, ma anche qui le query sono sempre preparate.
$user = User::where('email', $request->email)->first();Pericoli da evitare: non usare firstOrFail o find con input non sanitizzato se fai un cast implicito. Laravel protegge comunque, ma abituati a passare sempre variabili scalari.
Se devi fare query con whereIn o whereBetween, usa array:
$users = User::whereIn('id', $request->input('ids', []))->get();
// Se ids non è un array, Laravel lancia eccezione o usa vuoto.3. Raw SQL con DB::statement e DB::select: il binding salva la vita
Ci sono casi in cui devi scrivere SQL puro per performance (es. reporting complessi). In questi casi, usa sempre DB::statement o DB::select con parametri.
DB::statement('UPDATE users SET points = points + ? WHERE id = ?', [$points, $userId]);Mai scrivere:
DB::statement("UPDATE users SET points = points + {$points} WHERE id = {$userId}");
// INJECTION! Basta che $userId sia "1; DROP TABLE users--" e perdi tutto.Casi concreti che abbiamo visto
Un cliente aveva un report custom con query raw costruita concatenando input JSON. In pochi minuti abbiamo riscritto tutto con binding. Risultato: query più veloci (il binding permette al database di memorizzare il piano di esecuzione) e vulnerabilità zero.
Un altro caso: un e-commerce con filtri dinamici. Usava whereRaw senza binding per ordinare per prezzo. Bastava un carattere per ottenere l’intera tabella degli ordini. Lo abbiamo risolto con orderByRaw('price ?', [$direction]).
Noi, di Meteora Web, abbiamo anche recuperato un backup dopo un attacco SQL Injection su un sito realizzato da terzi. Il database era stato esposto con tutti i dati dei clienti. La lezione: la sicurezza non è un optional, è una prassi di sviluppo.
Errori comuni anche in Laravel
- Usare
DB::rawsenza binding —DB::rawè per esprimere SQL non processato. Se ci concateni variabili, perdi la protezione. - Ignorare la validazione — Anche con binding, un input malformato può causare errori o bypass di logica. Valida sempre tipo, formato e lunghezza.
- Non proteggere le stored procedure — Se chiami stored procedure in MySQL, anche lì usa prepared statements.
- Dimenticare gli
orderBydinamici —orderBy('column', $direction)è sicuro se$directionè un valore controllato (es. 'asc' o 'desc'). Se accetti un input arbitrario, attento a SQL injection tramite alias.
Verifica che le tue query siano sicure
Puoi controllare il log delle query di Laravel per vedere se vengono usati prepared statements attivando DB::enableQueryLog(). In produzione, non lasciare abilitato, ma in sviluppo è utile.
DB::enableQueryLog();
$users = User::where('email', $request->email)->get();
dd(DB::getQueryLog());
// Noterai 'bindings' => [':email' => '...'] e query con ? placeholderInoltre, usa strumenti come Laravel Security Checker o RIPS per scansioni automatiche. Noi consigliamo di integrare un test di penetrazione periodico.
In sintesi — Cosa fare adesso
- Rivedi tutte le query raw nel codice: cerca
DB::raw,whereRaw,selectRaw,orderByRaw. Converti a binding immediatamente. - Usa sempre
DB::statementcon array associativo per query raw complesse. - Attiva la validazione robusta sui campi input con le regole di Laravel (
validateoFormRequest). - Controlla i log delle query in sviluppo per verificare i binding.
- Esegui un test di sicurezza con uno strumento come
sqlmapin ambiente di staging per simulare attacchi.
La SQL Injection non è un problema se scrivi codice pensando alla sicurezza dal primo carattere. In Laravel hai tutti gli strumenti: usali. E se hai un vecchio progetto PHP da controllare, noi siamo qui per aiutarti.
Sponsored Protocol