# SQL injection: prevenzione che regge

La SQL injection si previene con query parametrizzate, identificatori presi da elenchi chiusi e un utente database con permessi minimi. Cosa resta scoperto anche dopo.

> Pagina: https://ui.eneawork.it/blog/sql-injection-prevenzione
> Markdown: https://ui.eneawork.it/blog/sql-injection-prevenzione.md

*2026-11-01*

La SQL injection si previene con query parametrizzate e un utente database con permessi minimi. Il difetto è la stringa costruita a mano, non il testo che l'utente digita: se il valore e il comando viaggiano separati, il database non ha modo di interpretare il valore come SQL.

Il caso classico non è un attacco esotico. Un campo di ricerca, un ordinamento per colonna, un filtro «solo attivi»: ognuno dei tre punti, scritto con una concatenazione, apre la strada a leggere o cancellare tabelle che il form non nominava. Chi cerca solo nel corpo della POST lascia fuori header, cookie e parametri di ordinamento.

## Query parametrizzate, non stringhe pulite

Il driver manda il testo della query e i valori separati al server, e il database non interpreta mai un valore come comando. La stringa ' OR 1=1 -- diventa un testo da cercare, non una condizione. Il meccanismo è lo stesso in ogni linguaggio: $1 in PostgreSQL, un punto interrogativo in MySQL, un segnaposto nominato in SQLite. Le funzioni di escape manuale sono una seconda scelta: bastano una codifica sbagliata o un set di caratteri diverso e il filtro salta.

## I punti in cui il parametro non arriva

Il nome di una colonna in ORDER BY non è un valore: si sceglie da una lista chiusa di campi ammessi, mai dalla query string.,Una ricerca con LIKE richiede il valore parametrizzato e i jolly nella query: il testo dell'utente non deve contenere il segno di percentuale.,Un IN con un numero variabile di valori si costruisce con un array, non concatenando elementi.,Le query raw degli ORM sono SQL: se accettano una stringa, vanno parametrizzate come qualsiasi altra query.,Il nome di uno schema o di una tabella non si passa come parametro: si valida con un elenco e si cita l'identificatore.

## Minimo privilegio: la seconda metà della difesa

Una query parametrizzata protegge i valori. Un utente database che può leggere l'intera tabella utenti resta un problema quando una query sbagliata espone una colonna in più. L'account dell'applicazione riceve solo i permessi che le servono: SELECT, INSERT e UPDATE sulle tabelle giuste, nessun DDL e nessun accesso al catalogo di sistema. Le migrazioni usano un secondo account, attivo solo durante il deploy.

## L'input che non arriva dal form

Header, cookie, parametri di ordinamento, webhook, file importati e job pianificati portano lo stesso rischio di un campo di testo. Un filtro applicato al solo corpo della POST copre metà delle superfici. La regola non cambia: il dato non si valida per fiducia, si passa come parametro, e la validazione dei tipi resta un controllo in più, non una difesa.

## Errori e log

Un errore del database non torna al client: il messaggio di sintassi dice all'attaccante quale frammento ha funzionato e quale no. Rispondi 500 con un identificativo di richiesta e cerca il dettaglio nei log. Il formato standard per l'errore verso il client, con un tipo e un'istanza, è descritto in progettare un'API.

## Il controllo che puoi mettere in CI

Un grep non è un test di sicurezza, ma intercetta la regressione più comune: la query costruita per concatenazione o per interpolazione di stringa. Il comando sotto cerca le forme sospette nei file del progetto e verifica che l'utente dell'applicazione non sia un superutente. Le due righe non sostituiscono una revisione: servono a impedire che il difetto rientri dopo essere stato corretto.

# query costruite per concatenazione o interpolazione: da rivedere una per una rg -n "SELECT .* FROM .*\\+" --glob "*.{js,ts,py,php}" src/ # l'utente dell'applicazione non deve essere superutente psql "$DATABASE_URL" -c "select current_user, usesuper from pg_user where usename = current_user"

Il 1 novembre 2026 le API pubbliche di ui.eneawork.it hanno contato 32 blocchi nella categoria form e 34 in login, su 1.556 blocchi totali; /api/stats ha restituito 111.985 componenti e 12.808 icone da 1.472 librerie. Sono le superfici che mandano dati a un database: il metodo è una query agli endpoint, non una stima.

## Il limite da dichiarare

Nessun blocco del catalogo mette in sicurezza un server: la prevenzione sta nelle query e nei permessi, e un componente React non tocca nessuno dei due. Un web application firewall riduce il rumore degli scanner, ma non sostituisce la parametrizzazione e non intercetta una query raw scritta male. Resta un limite anche sul catalogo: la licenza è dichiarata dal publisher e va verificata sul singolo blocco prima di pubblicarlo.

## Tre record di catalogo

La categoria login raccoglie 34 blocchi con campo email e password. La categoria form ne ha 32, dai moduli di contatto a quelli multi-campo. I componenti table coprono la vista lato operatore, dove un errore di filtro si nota subito. L'anteprima è gratuita; il codice completo sta nel piano Pro.

Sul lato autenticazione il middleware non è un controllo sufficiente da solo: leggi middleware di autenticazione, sessione e cookie e il confronto tra server actions e API.

## FAQ

### Bastano le query parametrizzate per fermare la SQL injection?

Fermano la parte che riguarda i valori, che è la maggioranza dei casi. Restano scoperti gli identificatori costruiti a runtime, come nome di colonna e direzione di ordinamento, e i permessi troppo larghi dell'utente database. Servono tutti e tre i controlli.

### Un ORM elimina il rischio?

No. Un ORM parametrizza le query che genera, ma ogni metodo per SQL raw accetta una stringa e riapre il problema. Anche i frammenti di query composti a mano dentro l'ORM vanno parametrizzati.

### Come si passa un nome di colonna in ORDER BY?

Con una lista chiusa di valori ammessi, per esempio un dizionario che mappa «nome» sulla colonna reale. Il nome che arriva dalla query string non entra mai nella query: serve solo a scegliere una voce della lista.

### Il WAF non risolve il problema?

Riduce gli scanner automatici e il rumore nei log, ma non conosce la tua applicazione: una query costruita male resta sfruttabile anche dietro un WAF. Va considerato un livello in più, non la difesa principale.

### Che permessi deve avere l'utente database dell'applicazione?

Solo quelli che le servono: SELECT, INSERT e UPDATE sulle tabelle che usa, nessun DDL e nessun accesso al catalogo di sistema. Le migrazioni girano con un secondo account, usato solo dal deploy.
