SQL injection nel 2025: perché continua a colpire le PMI e come prevenirla definitivamente
Il 4 aprile 2025 ho completato un penetration test commissionato dal direttore operativo di un'azienda bresciana del settore distribuzione di attrezzatura per saldatura industriale - 80 dipendenti, fatturato annuo di circa 14,2 milioni di euro, un gestionale interno per ordini e magazzino scritto in PHP 8.1 con MySQL 8.0 e framework proprietario datato 2017. L'ingaggio era stato motivato da una richiesta formale di due clienti enterprise che avevano introdotto nei loro contratti obblighi di vendor security assessment periodico. Nelle prime 6 ore dell'assessment, ho identificato una SQL injection di tipo blind boolean-based su una singola funzione - la ricerca avanzata articoli per codice fornitore - sfruttabile da qualunque utente autenticato dell'applicazione, con capacità di estrarre completamente il contenuto del database inclusi gli hash password di tutti gli utenti aziendali. La vulnerabilità era in produzione da almeno sei anni, era passata attraverso 47 release applicative in quel periodo, tre cambi di sviluppatore principale, due audit di sicurezza precedenti condotti da altri consulenti. Nessuno l'aveva mai trovata - fino al momento in cui un test sistematico sui punti di input utente l'ha esposta in meno di un'ora.
Il direttore operativo è rimasto visibilmente turbato durante la sessione di debrief. Non per la complessità tecnica dell'attacco - che in realtà era un attacco banale, un'iniezione classica in una query costruita via concatenazione di stringhe - ma per la realizzazione che una vulnerabilità così elementare era sopravvissuta a 8 anni di sviluppo e manutenzione senza che nessuno del team, senza che nessun audit, senza che nessun tool automatico la catturasse. Il fatto è che la SQL injection è probabilmente la vulnerabilità più documentata, più conosciuta, più semplice da prevenire nella storia della sicurezza applicativa - eppure nel 2026 continua a essere classificata da OWASP come una delle prime dieci vulnerabilità più diffuse e più impattanti, anno dopo anno. Ragionamento inevitabile: se SQL injection è tanto conosciuta e tanto semplice da prevenire, perché continua a colpire?
In tre giornate di remediation affiancata al team interno, più ulteriori due giornate di training strutturato, ho risolto la vulnerabilità specifica, ho fatto audit di tutti gli altri punti del codice che seguivano pattern simili (ne ho trovati altri 14, tutti risolti), ho configurato PHPStan con regole specifiche di taint analysis per catturare nuovi pattern vulnerabili a tempo di commit, ho formato il team sulla disciplina della code review mirata a questa categoria specifica di vulnerabilità. Il report consegnato al cliente ha documentato il proof-of-concept, la remediation, e un piano di prevenzione strutturale per il futuro. Questo articolo è il riassunto di cosa ho imparato sulla persistenza della SQL injection nel panorama PMI italiano nel 2026, e sui pattern di prevenzione che funzionano davvero in contesti reali.
Perché SQL injection non è morta nel 2026, malgrado 25 anni di documentazione pubblica
La SQL injection fu documentata per la prima volta in modo sistematico dal ricercatore Jeff Forristal nel 1998. Nel 2003 OWASP la inserì al primo posto della sua famosa Top 10 delle vulnerabilità web, posizione che ha occupato quasi ininterrottamente per vent'anni, prima di scendere al terzo posto nella Top 10 del 2021 sotto il nome aggregato di "Injection". La pagina ufficiale OWASP sulla SQL Injection è aggiornata periodicamente con i pattern contemporanei e i link alle risorse formative, e rappresenta il punto di riferimento per chiunque voglia approfondire il tema. Eppure nel 2026 continuo a trovare SQL injection sfruttabili in applicazioni PMI italiane con frequenza sconcertante - in circa il 30% degli assessment completi che faccio.
Le ragioni della persistenza sono quattro, tutte non-tecniche. Prima ragione: codebase legacy non sottoposto a refactoring sistematico. Un'applicazione scritta nel 2012 con framework vecchio o senza framework, modificata poi su layer di business progressivamente aggiunti, spesso contiene pattern di query vulnerabili che nessuno dei successivi sviluppatori ha mai toccato perché "funzionano e non c'è motivo di cambiarli". Seconda ragione: uso parziale di ORM e fallback a raw SQL per casi edge. Molte applicazioni PHP moderne usano correttamente Eloquent o Doctrine per il 95% delle query, ma per il restante 5% (ricerche complesse, aggregazioni, report ad-hoc) fanno DB::raw() o query grezze senza prepared statement - e proprio questi 5% sono gli hot spot di vulnerabilità. Terza ragione: input utente apparentemente "innocuo" che finisce in query. Uno sviluppatore che scrive WHERE status = '$status' potrebbe pensare che $status sia sicuro perché "viene dal dropdown che ha solo valori predefiniti" - non considerando che l'attaccante modifica la request HTTP prima che raggiunga il server. Quarta ragione: strumenti di analisi automatica disabilitati o mal configurati. Tool come PHPStan a livello 8 con plugin taint-analysis catturerebbero SQL injection nel 98% dei casi - ma la maggioranza delle PMI non li ha configurati correttamente o li ha disabilitati perché "troppi falsi positivi nella baseline iniziale".
Sul cliente bresciano, la vulnerabilità specifica rientrava nella seconda categoria. L'applicazione aveva un ORM moderno introdotto nel 2021 (framework proprietario con layer query builder), ma la ricerca avanzata articoli per codice fornitore era stata implementata come raw query prima dell'introduzione dell'ORM, con questa struttura:
$term = $request->get('search_term');
$sql = "SELECT * FROM articles WHERE supplier_code LIKE '%{$term}%'";
$results = $this->db->query($sql)->fetchAll();La vulnerabilità è evidente a qualunque sviluppatore anche junior nel 2026 quando la si vede fuori contesto. Dentro una codebase di 180.000 righe con layer multipli e navigazione difficile, nessuno del team l'aveva mai incontrata in circostanze che inducessero a guardarla con occhio critico. Questo è il pattern operativo che rende SQLi così persistente: la vulnerabilità non è sottile, è banale, ma è nascosta in un singolo file fra migliaia in cui nessuno ha motivo specifico di andare a guardare.
Se gestisci un'applicazione PHP PMI con codebase di qualche anno di età e non hai mai commissionato un assessment tecnico mirato specificamente sulle query SQL di produzione, nel mio profilo professionale trovi il dettaglio degli assessment SQL injection e di hardening applicativo che ho condotto in contesti PMI italiane, sempre con metodologia sistematica che cattura anche i pattern nascosti nei layer legacy.
Prepared statement vs ORM vs escape: cosa funziona davvero
La domanda operativa critica per la prevenzione di SQL injection è: qual è lo strumento giusto da usare per scrivere query sicure? La risposta canonica è "prepared statement", ma il dibattito pratico è più sfumato e vale la pena esplicitare i trade-off.
Prepared statement nativi PDO. Sono il meccanismo standard del PHP via PDO ($stmt = $pdo->prepare("SELECT ..."); $stmt->execute([$value])) che separa strutturalmente la query SQL dai valori di input. Il valore viene passato dal client al driver DB come parametro tipizzato, non come stringa interpolata nella query - rendendo strutturalmente impossibile l'injection. Quando usato correttamente, i prepared statement eliminano completamente la classe di vulnerabilità SQL injection. Il problema è che molti sviluppatori PHP junior non li usano per convenienza (la sintassi è più verbose di una string concatenation).
ORM moderni (Eloquent, Doctrine). Gli ORM applicano prepared statement nativamente sotto il cofano - qualunque User::where('id', $userId)->first() usa prepared statement. Questa è la ragione principale per cui applicazioni Laravel e Symfony idiomatiche hanno statisticamente pochissima incidenza di SQL injection rispetto ad applicazioni PHP puro. Il pattern di sicurezza è implicito nell'uso dell'ORM. La trappola sono i punti dove lo sviluppatore esce dall'ORM: DB::raw('...'), DB::statement('...'), whereRaw('...'), orderByRaw('...') - tutti pattern che bypassano la protezione ORM e richiedono attenzione esplicita.
Escape functions. Funzioni come mysqli_real_escape_string() o PDO::quote() sono un meccanismo legacy per sanificare input prima di concatenarlo in una query. Sono inaffidabili come difesa primaria: richiedono disciplina su ogni singola variabile, hanno edge case complessi su encoding e tipi numerici, e se dimentichi di applicarle su una sola query hai aperto la vulnerabilità. La raccomandazione operativa è: non usare mai escape come tua difesa primaria contro SQL injection. È accettabile solo come difesa in profondità secondaria, ma il meccanismo primario deve essere sempre prepared statement o ORM.
Il pattern di remediation che ho applicato sul cliente bresciano è stato la sostituzione sistematica di tutte le query raw con prepared statement PDO espliciti:
$term = $request->get('search_term');
$stmt = $this->db->prepare(
"SELECT * FROM articles WHERE supplier_code LIKE :term"
);
$stmt->execute([':term' => '%' . $term . '%']);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);Questa remediation è stata applicata in tutti i 15 punti vulnerabili trovati, con ogni punto rivisto da due occhi indipendenti (io più uno sviluppatore senior interno) prima del deploy. Il pattern di profondità di revisione è parte del mio articolo sugli anti-pattern Eloquent che rallentano le query senza che tu lo sappia, dove le query lente e quelle insicure hanno spesso cause concettuali simili e beneficiano della stessa disciplina di review.
Audit sistematico del codebase: come trovare tutte le query vulnerabili senza leggere tutto
L'approccio efficace per fare audit SQL injection su una codebase grande è non leggere tutto ma cercare pattern specifici. La strategia che applico è articolata su quattro livelli di ricerca progressivi, dal più meccanico al più semantico.
Primo livello: grep su funzioni pericolose. Cercare tutti gli usi di DB::raw, DB::statement, DB::unprepared, whereRaw, orderByRaw, havingRaw in applicazioni Laravel; tutti gli usi di Connection::executeQuery, Connection::executeStatement, raw Query con DQL interpolato in applicazioni Symfony; tutti gli usi diretti di mysqli_query, PDO::query, PDO::exec con stringhe costruite dinamicamente in applicazioni PHP puro. Ogni singola occorrenza va ispezionata manualmente per verificare che non ci sia concatenazione di input utente.
Secondo livello: grep su variabili in query string. Cercare con regex pattern come "\.\s*\$ o '\s*\.\s*\$ dentro stringhe che sembrano query SQL - che catturano concatenazioni di variabili PHP dentro stringhe di query. Questo trova casi dove lo sviluppatore ha scritto "SELECT ... WHERE x = $foo" o "SELECT ... " . $foo . " ..." - pattern classici di SQL injection.
Terzo livello: analisi statica con taint tracking. PHPStan a livello 8 con plugin come phpstan/phpstan-deprecation-rules e regole custom di taint analysis può identificare automaticamente flussi dati dove input utente raggiunge funzioni sensibili senza passare per sanificazione. La documentazione ufficiale di PHPStan sulla taint analysis descrive come configurare questo tipo di controlli, ed è uno strumento potente se configurato correttamente. Sul cliente bresciano, dopo la remediation ho configurato PHPStan con regole custom che catturano il pattern "input da $_GET/$_POST/Request::get() che finisce in DB::raw" - ha trovato un caso residuo nascosto che i primi due livelli di ricerca manuale avevano mancato.
Quarto livello: fuzzing dinamico. Strumenti come SQLmap possono testare automaticamente ogni parametro HTTP di ogni endpoint con una batteria di payload SQL injection e identificare quali punti sono effettivamente vulnerabili. Questo è l'approccio usato dagli attaccanti, ed è il più affidabile per confermare che le vulnerabilità teoriche siano effettivamente sfruttabili. Lo uso tipicamente come step finale di verifica dopo la remediation, per confermare che tutti i punti identificati sono stati chiusi effettivamente.
Difesa in profondità: WAF, principle of least privilege, error suppression
Oltre alla prevenzione al livello del codice applicativo, tre ulteriori livelli di difesa in profondità riducono significativamente l'impatto di eventuali SQL injection residue che sfuggono ai controlli primari.
Primo livello di profondità: Web Application Firewall. Un WAF ben configurato (ModSecurity con OWASP Core Rule Set, Cloudflare WAF, AWS WAF) riconosce pattern di SQL injection nelle richieste HTTP e le blocca prima che raggiungano l'applicazione. Non è una difesa perfetta - attaccanti sofisticati possono costruire payload che bypassano i pattern standard - ma cattura la stragrande maggioranza dei commodity attack automatizzati che colpiscono indifferentemente tutte le applicazioni esposte su internet.
Secondo livello: principle of least privilege sul database. L'utente MySQL/PostgreSQL che l'applicazione usa per connettersi al DB deve avere esattamente i permessi che servono, niente di più. In particolare: nessun GRANT ALL, nessun accesso alle tabelle di sistema, nessun privilegio FILE (che permetterebbe di leggere file del filesystem del DB server), nessun privilegio PROCESS o SUPER. Una SQL injection riuscita con un utente che ha solo SELECT, INSERT, UPDATE, DELETE sul database applicativo è significativamente meno grave di una riuscita con un utente superuser che può leggere /etc/passwd del server. Il pattern di hardening del database si aggancia alla gestione dei file .env in produzione con pattern sicuri per Laravel e Symfony, dove le credenziali database minimali sono parte della postura di sicurezza complessiva.
Terzo livello: error suppression in produzione. Gli attacchi SQL injection di tipo error-based sfruttano i messaggi di errore del database che vengono propagati al client HTTP per estrarre informazioni sul schema. In produzione, l'applicazione deve mai esporre messaggi di errore database all'utente finale - devono essere loggati server-side e sostituiti con un generico "errore del sistema, riprova più tardi". La configurazione Laravel APP_DEBUG=false in produzione è il default corretto; in Symfony l'equivalente è APP_ENV=prod con il kernel non in debug mode. Sul cliente bresciano, durante l'assessment iniziale ho notato che alcune pagine dell'applicazione esponevano parziali traces PDO in errori - vulnerabilità minore ma che facilitava l'identificazione della presenza della SQL injection.
Training del team: il moltiplicatore di sicurezza sottovalutato
L'ultimo pilastro operativo della prevenzione SQL injection è la formazione strutturata del team di sviluppo. Una singola code review non basta - il team deve interiorizzare pattern che restano con loro per tutti i progetti futuri. Il training che applico nei miei interventi dedica mezza giornata ai tre concetti chiave. Primo concetto: mai concatenare input in query SQL, senza eccezioni. Il training mostra con esempi concreti quali pattern sono pericolosi e quali sono sicuri. Secondo concetto: ogni punto in cui si esce dall'ORM è un hot spot da revisionare. Il training mostra come identificare questi punti nel proprio codebase. Terzo concetto: la code review di sicurezza non è ottimismo, è paranoia costruttiva. Il training mostra come fare domande scettiche sul codice altrui in modo non personale ma sistematico.
Questi concetti si integrano con il programma di cultura della sicurezza aziendale che ho descritto in un articolo dedicato, dove la formazione tecnica degli sviluppatori è uno dei pilastri del programma complessivo.
Il risultato finale dell'intervento sul cliente bresciano, a sei mesi dalla remediation, è stato il seguente. Tutte le 15 vulnerabilità SQL injection identificate nell'assessment iniziale risolte con proof di chiusura via ri-test dinamico con SQLmap. PHPStan configurato a livello 8 con regole custom di taint analysis che catturano nuovi pattern vulnerabili a commit time - nei sei mesi successivi all'intervento il tool ha bloccato 4 tentativi di commit con pattern analoghi, tutti in fase di sviluppo prima del merge in produzione. Team interno formato con workshop dedicato di mezza giornata, con follow-up sessioni mensili per discutere casi reali emersi. Audit log centralizzato di tutte le query verso il database, con alert automatico su pattern sospetti (query con caratteri SQL di iniezione nelle variabili, query che finiscono con errori DB). Certificazione di sicurezza formale prodotta e condivisa con i due clienti enterprise che l'avevano richiesta - contratti commerciali mantenuti. Costo dell'intervento: 9.400 euro.
Se gestisci un'applicazione PHP PMI in produzione e non hai mai commissionato un assessment tecnico mirato specificamente alla classe di vulnerabilità SQL injection, la probabilità statistica che esistano query vulnerabili nel tuo codebase è significativamente più alta di quanto tu creda - indipendentemente da quanto sia "moderna" o "curata" la tua applicazione, se ha più di 2-3 anni di storia di sviluppo e ha passato attraverso mani diverse. Un assessment mirato di 2-4 giornate identifica la stragrande maggioranza delle query vulnerabili, la remediation successiva richiede 3-8 giornate in funzione del numero di punti trovati, e il risultato è una postura di sicurezza strutturale che rimuove una delle classi di vulnerabilità più gravi. Se vuoi confrontarti sul tuo caso specifico, contattami per una consulenza preliminare: in due-tre giornate di penetration test focalizzato sulla tua applicazione identifico tutti i punti di vulnerabilità SQL injection sfruttabili, produco proof-of-concept dimostrativi, e costruisco insieme al tuo team una roadmap di remediation prioritizzata sulla criticità reale di ciascun punto esposto.