In breve: la SQL injection in PHP si previene in modo affidabile non inserendo mai i valori nei testi SQL, ma passandoli sempre tramite prepared statement con segnaposto. Per i nomi di colonna e di tabella, che non si possono passare come segnaposto, vale una whitelist fissa. In aggiunta, un utente di database con diritti minimi limita il danno se comunque resta una vulnerabilità.
Perché la SQL injection non deve uscire dal radar dopo PHP2Shell
A ottobre 2026 molto ruota attorno a PHP2Shell, due vulnerabilità critiche nell'interprete PHP, corrette dal 21/10/2026 in PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35. In quei giorni molti team hanno aggiornato i server e riavviato i processi. È giusto e urgente.
Uno stato attuale di PHP non dice però nulla sul codice proprio. La SQL injection è da anni tra le debolezze più frequenti nelle applicazioni web e nella OWASP Top 10 rientra nella categoria Injection. Chi sta comunque pianificando controlli di sicurezza dovrebbe esaminare subito anche gli accessi al database della propria applicazione.
La buona notizia: la SQL injection è una delle poche debolezze che si possono escludere quasi del tutto con una regola semplice, applicata con coerenza.
Come nasce la SQL injection in PHP?
La SQL injection nasce quando un'applicazione tratta l'input dell'utente come parte di un comando SQL invece che come semplici dati. Tipico è comporre una query da blocchi di testo e variabili. Se un visitatore può determinare il contenuto di una simile variabile, può in certe circostanze cambiare il significato del comando.
Le conseguenze vanno dalla lettura di dati di clienti altrui alla modifica di prezzi o permessi, fino alla cancellazione di intere tabelle. A seconda di database e diritti, una vulnerabilità può consentire anche accessi che vanno oltre il database. Se sono coinvolti dati personali, può sorgere un obbligo di notifica ai sensi del GDPR.
Gli input non sono soltanto i campi di un modulo. Anche i parametri URL, i cookie, gli header HTTP, i file caricati, le risposte di interfacce esterne e i dati già salvati nel proprio database vanno considerati non affidabili.
Prepared statement con PDO e mysqli
I prepared statement inviano al database il comando SQL e i valori separatamente. I valori non vengono mai interpretati come parte del comando. Con PDO una query sicura appare così:
<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
'id' => (int) $_GET['id'],
'status' => 'aktiv',
]);
$kunde = $stmt->fetch();
L'opzione PDO::ATTR_EMULATE_PREPARES => false fa sì che il database usi veri prepared statement, nella misura in cui il driver lo supporta. Con mysqli vale lo stesso principio:
<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();
Anche gli elenchi per IN (...) si possono rappresentare in modo sicuro creando un segnaposto proprio per ogni valore e passando i valori separatamente.
<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);
Casi particolari: nomi di colonna, ordinamento e SQL grezzo
I segnaposto funzionano solo per i valori, non per nomi di tabella, nomi di colonna o direzioni di ordinamento. Proprio qui nascono nella pratica molte vulnerabilità, ad esempio con tabelle ordinabili o filtri. La soluzione è un elenco fisso di valori consentiti:
<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';
$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");
Framework e CMS includono strumenti propri. In WordPress se ne occupa $wpdb->prepare(), in Laravel e Symfony i query builder e gli ORM. Non appena lì si usa SQL grezzo, ad esempio tramite funzioni con «raw» nel nome, valgono di nuovo le regole di questa sezione. Indicazioni su Laravel si trovano in Mettere in sicurezza le applicazioni Laravel.
Cerchi nel codice in modo mirato i testi SQL in cui vengono inserite variabili. Un inizio semplice è una ricerca di query( e ->raw in combinazione con $_GET, $_POST o $_REQUEST.
Individuare la SQL injection nel codice esistente
Con il software nuovo la regola si può rispettare fin dall'inizio. È più difficile con applicazioni cresciute negli anni. Lì vale la pena una ricerca sistematica dei punti in cui i testi SQL vengono composti con variabili:
# Comandi SQL in cui vengono inserite variabili
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Uso diretto dei dati della richiesta nelle funzioni di query
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
Non ogni riscontro è una vulnerabilità, ad esempio quando la variabile contiene solo un nome di tabella fisso. Ogni punto trovato dovrebbe però essere controllato una volta con attenzione e, se necessario, convertito a prepared statement. Gli strumenti di analisi statica del codice aiutano a riconoscere in modo permanente tali schemi nella pipeline di build, così che i nuovi errori non vadano nemmeno online.
Limitare il danno: diritti, messaggi di errore, verifica
Il codice sicuro è la prima linea di difesa. Altri livelli limitano il danno se una vulnerabilità viene comunque trascurata:
- Diritti minimi: l'utente di database del sito di solito ha bisogno solo di
SELECT,INSERT,UPDATEeDELETEsul proprio database, non di diritti per creare utenti o accedere ad altri database. - Nessun messaggio di errore verso l'esterno:
display_errors = Offnel php.ini. Gli errori del database vanno nel log, non nel browser. - Separazione per applicazione: ogni sito riceve un proprio utente di database.
- Tenere aggiornate le dipendenze: le vulnerabilità si trovano anche nelle librerie, come mostra la guida su composer audit.
- Far verificare: code review e analisi automatizzata trovano molti punti, un penetration test mostra che cosa è effettivamente raggiungibile dall'esterno.
-- Esempio: utente di database solo con i diritti necessari
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
Una debolezza affine riguarda l'output dei dati nel browser. Come metterlo in sicurezza lo spiega la guida Prevenire il cross-site scripting in PHP.
Come aiuta concretamente PHP SOS?
- Verifichiamo in modo sistematico gli accessi al database della Sua applicazione alla ricerca di query non sicure.
- Diamo priorità ai ritrovamenti in base a raggiungibilità e dati coinvolti e mostriamo correzioni concrete.
- Portiamo diritti del database e gestione degli errori a un livello sicuro.
- Dopo la correzione testiamo di nuovo e documentiamo il risultato.
Una verifica indipendente è descritta nel servizio Audit di sicurezza e penetration test.
Domande frequenti
Basta trattare gli input con addslashes o real_escape_string?
No, è soggetto a errori. L'escaping dipende dal set di caratteri e dal contesto ed è facile dimenticarlo in un punto. I prepared statement separano per principio comando e dati e sono perciò la via raccomandata.
Un ORM protegge automaticamente dalla SQL injection?
In larga parte, finché usa le funzioni di query dell'ORM. Non appena scrive SQL grezzo o inserisce valori in ordinamenti e nomi di colonna, valgono le stesse regole che senza ORM.
Un Web Application Firewall aiuta contro la SQL injection?
Un WAF può intercettare schemi noti e guadagnare tempo. Non sostituisce però un codice sicuro, perché le regole basate sui dati della richiesta si possono aggirare e talvolta colpiscono input legittimi.
L'aggiornamento di PHP contro PHP2Shell chiude anche le vulnerabilità di SQL injection?
No. PHP2Shell riguarda l'interprete PHP. La SQL injection è un errore nel codice applicativo e resta, a prescindere dalla versione di PHP in uso.