Rischi per la sicurezza
I database contengono spesso dati sensibili e consentono di eseguire operazioni pericolose. Per lavorare in sicurezza con Nette Database, gli aspetti chiave sono:
- Comprendere la differenza tra API sicure e non sicure.
- Utilizzare query parametrizzate
- Convalidare correttamente i dati in ingresso
Che cos'è l'iniezione SQL?
L'iniezione SQL è il rischio di sicurezza più grave quando si lavora con i database. Si verifica quando l'input dell'utente non filtrato diventa parte di una query SQL. Un aggressore può inserire i propri comandi SQL e quindi:
- estrarre dati non autorizzati
- Modificare o cancellare dati nel database
- bypassare l'autenticazione
Lo stesso vale per Database Explorer:
Query parametriche sicure
La difesa fondamentale contro le SQL injection è rappresentata dalle query parametrizzate. Nette Database offre diversi modi per utilizzarle.
Il modo più semplice è quello di utilizzare i segnaposto dei punti interrogativi:
Questo vale per tutti gli altri metodi di Database Explorer che consentono di inserire espressioni con segnaposto e parametri con punto interrogativo.
Per le clausole INSERT
, UPDATE
, o WHERE
è possibile passare i valori in una
matrice:
Convalida dei valori dei parametri
Le query parametrizzate sono la pietra miliare del lavoro sicuro sui database. Tuttavia, i valori passati in esse devono essere sottoposti a diversi livelli di convalida:
Controllo del tipo
Assicurare il tipo di dati corretto dei parametri è fondamentale: è una condizione necessaria per utilizzare in modo sicuro Nette Database. Il database presuppone che tutti i dati in ingresso abbiano il tipo di dati corretto corrispondente alla colonna.
Ad esempio, se $name
negli esempi precedenti diventasse inaspettatamente un array anziché una stringa, Nette
Database tenterebbe di inserire tutti i suoi elementi nella query SQL, dando luogo a un errore. Pertanto, non utilizzare mai**
dati non validati da $_GET
, $_POST
o $_COOKIE
direttamente nelle query del database.
Convalida del formato
Il secondo livello verifica il formato dei dati, ad esempio assicurandosi che le stringhe siano codificate UTF-8 e che la loro lunghezza corrisponda alla definizione della colonna, oppure verificando che i valori numerici rientrino nell'intervallo consentito per il tipo di dati della colonna.
A questo livello, si può fare parzialmente affidamento sul database stesso: molti database rifiutano i dati non validi. Tuttavia, il comportamento può variare: alcuni possono troncare silenziosamente le stringhe lunghe o tagliare i numeri fuori dall'intervallo.
Convalida specifica del dominio
Il terzo livello prevede controlli logici specifici per l'applicazione. Ad esempio, la verifica che i valori delle caselle di selezione corrispondano alle opzioni disponibili, che i numeri rientrino in un intervallo previsto (ad esempio, età 0–150 anni) o che le relazioni tra i valori abbiano senso.
Metodi di convalida raccomandati
- Utilizzare Nette Forms, che gestisce automaticamente la convalida di tutti gli input.
- Utilizzare i Presenter e dichiarare i tipi di dati dei parametri nei metodi
action*()
erender*()
. - Oppure implementare un livello di validazione personalizzato usando strumenti PHP standard come
filter_var()
.
Lavorare in sicurezza con le colonne
Nella sezione precedente è stato illustrato come convalidare correttamente i valori dei parametri. Tuttavia, quando si utilizzano gli array nelle query SQL, occorre prestare la stessa attenzione alle loro chiavi.
Per i comandi INSERT e UPDATE, questa è una grave falla nella sicurezza: un utente malintenzionato può inserire
o modificare qualsiasi colonna del database. Potrebbe, ad esempio, impostare is_admin = 1
o inserire dati arbitrari
in colonne sensibili (nota come Mass Assignment Vulnerability).
Le condizioni WHERE sono ancora più pericolose perché possono contenere operatori:
Un utente malintenzionato può utilizzare questo approccio per scoprire sistematicamente gli stipendi dei dipendenti. Potrebbe iniziare con una query per stipendi superiori a 100.000, poi inferiori a 50.000 e, restringendo gradualmente l'intervallo, potrebbe rivelare gli stipendi approssimativi di tutti i dipendenti. Questo tipo di attacco è chiamato enumerazione SQL.
I metodi where()
e whereOr()
sono ancora più flessibili e
supportano espressioni SQL, compresi operatori e funzioni, sia nelle chiavi che nei valori. Ciò consente a un aggressore di
eseguire complesse iniezioni SQL:
Questo attacco termina la condizione originale con 0)
, aggiunge la propria SELECT
utilizzando
UNION
per ottenere dati sensibili dalla tabella users
e chiude con una query sintatticamente corretta
utilizzando WHERE (1)
.
Whitelist delle colonne
Per lavorare in sicurezza con i nomi delle colonne, è necessario un meccanismo che garantisca che gli utenti possano interagire solo con le colonne consentite e non possano aggiungerne di proprie. Il tentativo di individuare e bloccare i nomi di colonna pericolosi (blacklist) è inaffidabile: un aggressore può sempre trovare un nuovo modo di scrivere un nome di colonna pericoloso che non è stato previsto.
Pertanto, è molto più sicuro invertire la logica e definire un elenco esplicito di colonne consentite (whitelisting):
Identificatori dinamici
Per i nomi dinamici di tabelle e colonne, utilizzare il segnaposto ?name
. Questo assicura il corretto escape
degli identificatori secondo la sintassi del database (ad esempio, usando i backtick in MySQL):
Importante: utilizzare il simbolo ?name
solo per i valori attendibili definiti nel codice dell'applicazione. Per
i valori forniti dall'utente, utilizzare nuovamente una whitelist. In caso contrario, si
rischiano vulnerabilità di sicurezza: