Una SQL injection è una vulnerabilità che permette a input non affidabili di alterare una query SQL costruita dall’applicazione. Il problema non è il semplice fatto che un sito abbia moduli, commenti o parametri nell’URL: nasce quando quei dati finiscono dentro una query in modo tale da poter essere interpretati come codice SQL invece che come dati.

Le conseguenze possono andare dalla lettura di informazioni che l’utente non dovrebbe vedere alla modifica o cancellazione dei dati, fino all’aggiramento di controlli applicativi. L’impatto reale dipende però da come è costruita l’applicazione, dal database e soprattutto dai privilegi concessi all’account con cui l’applicazione si collega al DB.

La difesa più importante è quindi strutturale: separare il codice SQL dai valori ricevuti dall’esterno usando query parametrizzate o prepared statement. Validazione, least privilege, aggiornamenti, logging e WAF completano la protezione, ma non sostituiscono una query scritta correttamente.

In questa guida vediamo come funziona una SQL injection, quali forme può assumere, come riconoscere il meccanismo che la rende possibile e come prevenirla. Poi scendiamo nel caso WordPress, dove la regola pratica è semplice: usare prima le API native e, quando serve SQL personalizzato, preparare correttamente le query.

Cos’è una SQL injection

SQL, acronimo di Structured Query Language, è il linguaggio usato da molti sistemi per leggere e modificare dati in un database relazionale. Un’applicazione può usarlo, per esempio, per recuperare un utente, caricare un prodotto, salvare un ordine o cercare un contenuto.

Una query può essere perfettamente legittima:

SELECT id, email
FROM users
WHERE email = ?

Il segnaposto ? rappresenta un valore che verrà fornito separatamente. Questo dettaglio è fondamentale: il database riceve da una parte la struttura della query e dall’altra il valore da usare.

Una SQL injection diventa possibile quando l’applicazione costruisce invece la query combinando direttamente stringhe SQL e dati non affidabili.

Dati e codice SQL devono restare separati

Il modello mentale più utile è questo:

codice SQL + dati separati → query parametrizzata → il valore resta un dato

contro:

codice SQL + input concatenato → una sola stringa SQL → l’input può alterare la query

L’input non deve necessariamente provenire da una casella di testo. Può arrivare da parametri GET o POST, cookie, header HTTP, API, importazioni, webhook o dati già salvati in precedenza e riutilizzati successivamente.

Per questo dire che “ogni modulo è vulnerabile” sarebbe sbagliato. Il punto critico è il percorso che l’input compie fino alla query.

SQL injection e Cross-Site Scripting (XSS) appartengono entrambe alla famiglia delle vulnerabilità di injection, ma colpiscono contesti diversi: la prima altera l’interazione con il database, mentre XSS riguarda codice che può essere interpretato nel contesto del browser.

Come funziona un attacco SQL injection

Immagina un’applicazione PHP che riceve un indirizzo email e lo inserisce direttamente nella query:

$email = $_POST['email'] ?? '';

$sql = "SELECT id, email
        FROM users
        WHERE email = '$email'";

Qui il valore di $email non viene passato al database come parametro. Viene incollato dentro la stringa SQL.

La vulnerabilità non dipende dal nome della variabile né dal fatto che l’input sia stato ricevuto da un form: dipende dal fatto che un dato controllabile dall’esterno può modificare la struttura della query.

L’OWASP SQL Injection Prevention Cheat Sheet raccomanda come difesa primaria i prepared statement con query parametrizzate proprio perché mantengono distinti codice e dati.

Cosa può ottenere un attaccante

L’effetto di una SQL injection dipende dal punto vulnerabile e dai permessi disponibili. In scenari diversi un attaccante può riuscire a:

  • leggere record che l’applicazione non dovrebbe mostrare;
  • modificare o eliminare dati;
  • interferire con la logica di autenticazione o autorizzazione;
  • ricavare informazioni sulla struttura del database;
  • eseguire operazioni consentite all’account database usato dall’applicazione.

Non è corretto però trasformare ogni SQL injection in una compromissione totale del server. Passare dal database al sistema operativo richiede condizioni aggiuntive, come funzionalità specifiche del DBMS, configurazioni pericolose o privilegi molto elevati.

Questa è anche la ragione per cui il principio del minimo privilegio conta: non elimina la vulnerabilità, ma può ridurre in modo significativo ciò che una query compromessa riesce a fare.

Tipi di SQL injection

Le SQL injection non si presentano tutte nello stesso modo. Le categorie servono soprattutto a capire come l’applicazione restituisce o lascia inferire il risultato della query.

La Web Security Academy di PortSwigger distingue diversi scenari operativi, tra cui attacchi che restituiscono direttamente i risultati, SQL injection blind e tecniche out-of-band.

In-band SQL injection

Nelle SQL injection in-band, la stessa comunicazione usata per inviare l’input permette anche di ottenere informazioni utili dalla risposta.

Error-based SQL injection

L’applicazione restituisce errori del database sufficientemente dettagliati da rivelare informazioni sulla query, sulla struttura del database o sui dati elaborati.

È uno dei motivi per cui in produzione conviene evitare di mostrare all’utente errori SQL dettagliati. Gli errori tecnici vanno registrati in modo sicuro nei log, non esposti liberamente nel frontend.

UNION-based SQL injection

Quando il punto vulnerabile e la query lo consentono, l’operatore SQL UNION può essere sfruttato per combinare i risultati della query originale con quelli provenienti da un’altra selezione.

Dal punto di vista difensivo, però, non cambia la causa: se il valore esterno è gestito come parametro anziché concatenato alla query, non può trasformarsi in una nuova parte dell’istruzione SQL.

Blind SQL injection

Una SQL injection è detta blind quando l’applicazione non mostra direttamente il risultato della query o gli errori del database, ma il comportamento della risposta può comunque rivelare informazioni.

Boolean-based blind

L’attaccante cerca differenze nel comportamento dell’applicazione in base al fatto che una determinata condizione risulti vera o falsa.

Time-based blind

L’informazione viene inferita osservando differenze nei tempi di risposta prodotte dall’esecuzione condizionale della query.

Il fatto che il frontend non mostri errori SQL, quindi, non dimostra da solo che una pagina sia immune alle SQL injection.

Out-of-band SQL injection

In alcuni contesti il database può generare una comunicazione attraverso un canale diverso da quello usato per inviare la richiesta iniziale.

Queste tecniche dipendono molto dal DBMS, dalle funzioni abilitate, dalla rete e dai privilegi. Per una strategia di prevenzione il punto resta lo stesso: impedire che input non affidabile modifichi la query e ridurre al minimo privilegi e capacità non necessarie.

Second-order SQL injection

La second-order SQL injection rende evidente un errore frequente: considerare sicuro un dato soltanto perché è già stato salvato nel database.

Un’applicazione può memorizzare inizialmente un valore senza problemi e, in un secondo momento, recuperarlo e concatenarlo in una nuova query. La vulnerabilità si manifesta quindi quando il dato viene riutilizzato in un contesto SQL insicuro, non necessariamente quando è stato ricevuto la prima volta.

Esempio di SQL injection: query vulnerabile e query sicura

Il confronto più utile non è fra “input buono” e “input cattivo”, ma fra query costruita concatenando stringhe e query parametrizzata.

Query vulnerabile

$email = $_POST['email'] ?? '';

$sql = "SELECT id, email
        FROM users
        WHERE email = '$email'";

$result = $pdo->query($sql);

Anche se il codice sembra semplice, l’applicazione sta componendo un’unica stringa che contiene sia SQL sia un valore controllabile dall’esterno.

Query parametrizzata

$email = $_POST['email'] ?? '';

$stmt = $pdo->prepare(
    'SELECT id, email FROM users WHERE email = :email'
);

$stmt->execute([
    'email' => $email,
]);

$result = $stmt->fetch();

Qui la struttura SQL viene definita prima e il valore viene associato al parametro :email. Il database non deve indovinare dove finisce il dato e dove comincia il comando.

Questo è il cambio di architettura che conta davvero.

Confronto visivo tra input concatenato direttamente nella query SQL e valore passato separatamente come parametro
La protezione efficace nasce dalla separazione tra struttura SQL e valori, non dal tentativo di riconoscere ogni input pericoloso.

Validare l’email rimane sensato per verificare che il dato sia coerente con ciò che l’applicazione si aspetta, ma la validazione non deve diventare una scusa per tornare alla concatenazione delle stringhe.

Come prevenire una SQL injection

Non tutte le misure hanno lo stesso ruolo. Metterle sullo stesso piano porta facilmente a una falsa sensazione di sicurezza.

ControlloCosa faRuolo contro SQL injection
Query parametrizzate / prepared statementseparano codice SQL e valoridifesa primaria
Stored procedure costruite in modo sicuroincapsulano operazioni DB senza SQL dinamico insicurodifesa primaria possibile
Allowlist validationaccetta solo valori previstiprimaria in alcuni casi, altrimenti difesa aggiuntiva
Escapingtratta caratteri speciali secondo il DBMSfallback più fragile, non prima scelta
Least privilegelimita ciò che l’account DB può fareriduce l’impatto
WAFfiltra richieste sospette prima dell’applicazionemitigazione aggiuntiva
Aggiornamenti e patchrimuovono vulnerabilità note nel softwareriducono la superficie esposta
Logging e testaiutano a individuare problemi e tentativirilevamento e verifica

Usa prepared statement e query parametrizzate

È la misura con il miglior rapporto tra semplicità concettuale ed efficacia.

La query deve descrivere il comando SQL; i valori devono essere passati separatamente attraverso il meccanismo di binding previsto dal driver, framework o CMS.

Questo approccio non prova a riconoscere tutte le possibili stringhe “pericolose”. Evita a monte che il valore venga reinterpretato come parte del comando.

Valida gli input con allowlist quando il dominio lo permette

La validazione serve a stabilire se un valore appartiene al dominio previsto dall’applicazione.

Se un parametro può essere soltanto:

ASC
DESC

non ha senso accettare qualsiasi stringa e provare poi a ripulirla. Conviene mappare l’input su un insieme chiuso di valori ammessi.

L’allowlist è particolarmente importante nei punti della query in cui un normale bind parameter non può essere usato o quando devi scegliere elementi strutturali come un ordinamento o un identificatore.

Resta però valida una regola: un dato validato non diventa automaticamente sicuro da concatenare ovunque.

Non affidarti al solo escaping

L’escaping può essere necessario in alcuni contesti, ma è più fragile e dipende maggiormente dal database e dal modo in cui viene costruita la query.

OWASP considera l’escaping di tutto l’input una strategia fortemente sconsigliata come difesa primaria quando sono disponibili alternative migliori.

In pratica: se puoi risolvere il problema con binding e parametri, fallo lì.

Applica il principio del minimo privilegio al database

L’account usato dall’applicazione per collegarsi al database non dovrebbe avere automaticamente privilegi amministrativi.

Se una parte dell’applicazione deve soltanto leggere determinati dati, non ha motivo di poter creare tabelle o eliminare interi database.

Attenzione a non confondere questo livello con i ruoli WordPress. Rendere un utente WordPress “Editor” anziché “Amministratore” può essere utile per il controllo degli accessi al CMS, ma non equivale a limitare i privilegi dell’account MySQL/MariaDB usato dall’applicazione.

Non esporre errori SQL dettagliati agli utenti

Un messaggio di errore utile allo sviluppatore può essere troppo informativo per chi visita il sito.

In produzione conviene:

  1. mostrare all’utente un messaggio generico;
  2. registrare l’errore tecnico nei log appropriati;
  3. proteggere i log da accessi non autorizzati;
  4. evitare che credenziali, query complete o dati sensibili finiscano in output pubblico.

Questo non corregge una SQL injection, ma riduce la quantità di informazioni che l’applicazione rivela e migliora la capacità di indagine.

Mantieni CMS, framework, plugin e dipendenze aggiornati

Una query sicura nel tuo codice non rende automaticamente sicuro tutto il software installato.

Framework, CMS, plugin, temi, librerie e componenti custom possono contenere vulnerabilità proprie. Applicare gli aggiornamenti di sicurezza riduce la finestra in cui una vulnerabilità già corretta dal maintainer rimane sfruttabile sul tuo sito.

Su WordPress questo punto è particolarmente importante perché una parte rilevante della superficie applicativa deriva dai componenti installati.

Usa un WAF come difesa aggiuntiva, non come correzione del codice

Un Web Application Firewall può analizzare richieste HTTP e bloccare pattern compatibili con tentativi di SQL injection.

È utile soprattutto come defense in depth e, in alcuni casi, come mitigazione temporanea mentre viene distribuita una patch.

Il limite però deve essere chiaro: se il codice concatena input non affidabile dentro una query, il WAF non trasforma quella query in una query sicura. Può ridurre l’esposizione, non eliminare la causa.

Verifica il codice e testa soltanto sistemi per cui hai autorizzazione

Per trovare SQL injection prima che vengano sfruttate, il controllo più utile combina più livelli:

  • code review delle query e dei data flow;
  • strumenti SAST per cercare pattern insicuri nel codice;
  • DAST o vulnerability scanner in ambienti controllati;
  • test di sicurezza su staging;
  • penetration test autorizzati per applicazioni più critiche.

Gli scanner non sostituiscono la revisione del codice e non tutti i risultati sono veri positivi. Allo stesso modo, un test che non trova una vulnerabilità non dimostra che tutto il sistema sia sicuro.

SQL injection in WordPress: cosa cambia davvero

WordPress non cambia il principio di base della SQL injection: un valore non affidabile non deve diventare parte del codice SQL.

Quello che cambia è il modo più sicuro per lavorare con il database.

La documentazione ufficiale di WordPress sulle vulnerabilità comuni suggerisce prima di tutto di usare le API native quando esiste già una funzione WordPress adatta al compito. Se devi salvare un metadato di un post, per esempio, è normalmente preferibile usare l’API dedicata anziché scrivere a mano una INSERT sulla tabella wp_postmeta.

Questo riduce la quantità di SQL personalizzato che devi mantenere e ti fa lavorare attraverso astrazioni già previste dal CMS.

WordPress Core, plugin, temi e codice personalizzato

La superficie da controllare non è soltanto WordPress Core.

Una SQL injection può trovarsi in:

  • un plugin che interroga direttamente il database;
  • un tema con logica custom;
  • codice inserito in un plugin proprietario;
  • endpoint AJAX o REST sviluppati su misura;
  • integrazioni che salvano e poi riutilizzano dati esterni;
  • componenti non più mantenuti che contengono vulnerabilità note.

Per questo un sito può essere aggiornato nel Core ma rimanere vulnerabile attraverso un’estensione o del codice custom.

La guida alla sicurezza WordPress affronta il problema a un livello più ampio: aggiornamenti, accessi, hardening e monitoraggio servono a ridurre la superficie complessiva, mentre qui il focus rimane il percorso input → query → database.

Come usare $wpdb->prepare() nelle query personalizzate

Quando hai davvero bisogno di eseguire SQL personalizzato, WordPress mette a disposizione la classe $wpdb.

Il metodo $wpdb->prepare() prepara una query usando placeholder per valori stringa, interi, float e identificatori supportati.

Un esempio semplice:

global $wpdb;

$post_id = isset( $_GET['post_id'] )
    ? absint( $_GET['post_id'] )
    : 0;

$post = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT ID, post_title
         FROM {$wpdb->posts}
         WHERE ID = %d",
        $post_id
    )
);

Qui succedono due cose diverse:

  • absint() restringe il valore al tipo atteso dall’applicazione;
  • %d e $wpdb->prepare() fanno sì che il valore venga inserito nella query attraverso il meccanismo previsto da WordPress.

La validazione non sostituisce prepare(), e prepare() non elimina il bisogno di validare la logica applicativa.

Sanitizzazione WordPress e query parametrizzate non sono la stessa cosa

È facile confondere tre operazioni diverse.

Validazione
Decide se un dato è accettabile per il contesto.

Sanitizzazione
Normalizza o rimuove parti del dato per portarlo in una forma prevista.

Preparazione/escaping per SQL
Gestisce il valore nel contesto specifico della query.

Usare sanitize_text_field() su un valore non autorizza a concatenarlo in una query SQL. Se quel dato deve entrare in SQL personalizzato, va comunque usato il meccanismo corretto per la query.

Lo stesso vale al contrario: una query parametrizzata impedisce al valore di diventare codice SQL, ma non stabilisce se quel valore sia coerente con la logica della tua applicazione.

Le ricerche LIKE richiedono un’attenzione in più

Quando costruisci una ricerca SQL con LIKE, WordPress mette a disposizione $wpdb->esc_like() per gestire i caratteri speciali propri di quel costrutto.

Il pattern va poi passato a prepare(), non usato come scorciatoia per tornare alla concatenazione libera.

In altre parole:

dato grezzo → esc_like() per il pattern LIKE → prepare() per la query

L’ordine e il contesto contano.

WAF e plugin di sicurezza: cosa possono e cosa non possono fare

Plugin di sicurezza e firewall possono essere utili per:

  • filtrare richieste sospette;
  • registrare tentativi;
  • rilevare file modificati o malware;
  • aggiungere controlli sugli accessi;
  • applicare regole contro vulnerabilità note.

Per esempio, Wordfence combina firewall, scansione e funzioni di sicurezza per WordPress.

Ma se un plugin custom contiene una query vulnerabile, installare un plugin di sicurezza non corregge quella riga di codice. La vulnerabilità va rimossa alla fonte.

Questa distinzione evita il problema più comune nelle guide alla sicurezza: presentare uno strumento di mitigazione come se fosse equivalente allo sviluppo sicuro.

Come ridurre il rischio di SQL injection su un sito WordPress

Se gestisci un sito WordPress ma non sviluppi direttamente il codice, non hai bisogno di trasformarti in un database engineer. Devi però sapere quali controlli hanno davvero priorità.

Aggiorna Core, temi e plugin

Non rimandare indefinitamente gli aggiornamenti di sicurezza. Prima di applicarli su siti critici, usa un backup e un ambiente di staging quando il rischio di incompatibilità lo giustifica.

La sicurezza non richiede update “alla cieca”: richiede un processo che consenta di distribuire le patch senza lasciare il sito fermo su componenti vulnerabili.

Elimina componenti abbandonati o non necessari

Un plugin disattivato ma ancora presente sul server può comunque rappresentare codice da gestire. Un componente non mantenuto, inoltre, può diventare un problema quando emergono vulnerabilità e non arriva più una patch.

Ridurre la superficie significa anche chiedersi:

  • questo plugin serve ancora?
  • riceve aggiornamenti?
  • esiste un’alternativa mantenuta?
  • contiene funzionalità duplicate da altri componenti?

Controlla con attenzione il codice personalizzato

Più il sito contiene plugin proprietari, integrazioni e query custom, più diventa importante una revisione tecnica.

Cerca soprattutto:

input esterno
→ trasformazione
→ query SQL
→ prepare/binding presente?
→ autorizzazione presente?

La domanda non è semplicemente “il valore è stato sanitizzato?”, ma dove entra nella query e con quale meccanismo.

Aggiungi una protezione applicativa

Un WAF ben configurato può bloccare molti tentativi prima che raggiungano il codice vulnerabile e aggiungere telemetria utile.

Non deve però diventare una scusa per lasciare aperta una vulnerabilità conosciuta.

Usa plugin di sicurezza per il loro ruolo reale

Un plugin di sicurezza può aggiungere WAF, scansione malware, 2FA, file integrity monitoring e altri controlli. Se stai scegliendo uno strumento, valuta la funzione che ti serve invece di cercare genericamente un “plugin anti SQL injection”.

In altre parole:

plugin di sicurezza = livello aggiuntivo
query parametrizzate = correzione del difetto SQL

Sono due problemi diversi.

Mantieni backup e logging realmente utilizzabili

Il backup è utile soltanto se sai:

  • dove viene conservato;
  • quanto è recente;
  • quanto a lungo viene mantenuto;
  • se è separato dal sistema compromettibile;
  • se hai già verificato almeno una procedura di ripristino.

Allo stesso modo, i log devono aiutarti a ricostruire un incidente. Conservare migliaia di righe che nessuno controlla non equivale a monitorare la sicurezza.

Cosa fare se sospetti che una SQL injection abbia compromesso il sito

Quando sospetti una compromissione, l’obiettivo non è soltanto “far sparire l’errore”.

Devi capire quale vulnerabilità è stata sfruttata, cosa ha potuto fare l’attaccante e come impedire che accada di nuovo.

Non limitarti a installare un plugin di sicurezza

Installare un firewall dopo la compromissione può ridurre nuovi tentativi, ma non elimina:

  • file già modificati;
  • utenti amministratori creati dall’attaccante;
  • backdoor;
  • dati alterati;
  • credenziali esposte;
  • la query vulnerabile che ha aperto l’accesso.

La sequenza corretta parte dall’analisi dell’incidente.

Individua e correggi la vulnerabilità

Verifica log applicativi e server, componenti coinvolti, modifiche recenti e codice che interagisce con il database.

Se trovi una query vulnerabile, correggila. Se la vulnerabilità appartiene a un plugin o tema di terze parti, verifica se esiste una patch; in assenza di una correzione affidabile, valuta la disattivazione o sostituzione del componente.

Verifica database, file, utenti e credenziali

Una compromissione applicativa può lasciare tracce in più punti.

Controlla almeno:

  • file WordPress e file custom;
  • utenti amministrativi;
  • plugin e temi installati;
  • record del database che potrebbero essere stati modificati;
  • credenziali WordPress, hosting, database, FTP/SFTP e API potenzialmente esposte;
  • cron job e meccanismi di persistenza.

Se ruoti le credenziali senza rimuovere prima una backdoor ancora attiva, rischi semplicemente di consegnare anche le nuove credenziali all’attaccante.

Ripristina da una copia affidabile quando è la scelta corretta

Un backup può velocizzare il recupero, ma deve essere precedente alla compromissione e non deve reintrodurre la stessa vulnerabilità.

Ripristinare senza applicare la patch significa spesso tornare online con la stessa porta aperta.

Se il sito WordPress è già stato compromesso e non riesci a stabilire con affidabilità cosa sia stato alterato, può essere più prudente ricorrere a un intervento di rimozione malware WordPress che includa anche la ricerca della causa e l’hardening successivo.

Conclusione

La SQL injection non si previene con una singola checkbox di sicurezza.

Se sviluppi l’applicazione, la priorità è impedire che input non affidabile modifichi la struttura delle query: prepared statement, parametri e API sicure vengono prima di filtri e firewall.

Se gestisci un sito WordPress, la priorità cambia leggermente: mantieni aggiornati Core e componenti, elimina ciò che non serve, fai revisionare il codice personalizzato che accede al database e usa WAF e plugin di sicurezza come livelli aggiuntivi.

Se il sito è già compromesso, invece, non cercare soltanto di bloccare il prossimo tentativo. Trova la vulnerabilità, verifica ciò che è stato alterato, ripristina in modo controllato e chiudi la causa.

È questa la differenza tra ridurre i sintomi di un attacco e costruire una protezione reale contro le SQL injection.