Rischi per la sicurezza
I database contengono spesso dati sensibili e consentono operazioni pericolose. Nette Database offre una serie di funzioni di sicurezza. Tuttavia, è fondamentale capire la differenza tra API sicure e non sicure.
Iniezione SQL
L'iniezione SQL è il rischio di sicurezza più grave quando si lavora con i database. Si verifica quando un input dell'utente non controllato diventa parte di una query SQL. Un aggressore può iniettare i propri comandi SQL, ottenendo o modificando i dati nel database.
// Codice non sicuro - vulnerabile all'iniezione SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// L'attaccante può inserire qualcosa come: ' OR '1'='1
// La query risultante sarà:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Questo restituisce tutti gli utenti!
Lo stesso vale per Database Explorer:
// ❌ CODICE NON SICURO
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Query parametriche sicure
Il modo sicuro per inserire valori nelle query SQL è quello delle query parametrizzate. Nette Database offre diversi modi per utilizzarle.
Punti interrogativi segnaposto
Il metodo più semplice è quello di utilizzare dei punti interrogativi segnaposto:
// Query parametriche sicure
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);
// ✅ Condizione sicura in Explorer
$table->where('name = ?', $_GET['name']);
Lo stesso vale per tutti gli altri metodi di Database Explorer che consentono di inserire espressioni con punti interrogativi e parametri segnaposto.
I valori devono essere di tipo scalare (string
, int
, float
,
bool
) o null
. Se, ad esempio, è un array, Nette Database includerà tutti i suoi elementi nella query
SQL, il che può essere indesiderato, $_GET['name']
è un array, Nette Database includerà tutti i suoi elementi
nella query SQL, il che può essere indesiderato.
Array di valori
Per le clausole INSERT
, UPDATE
o WHERE
si possono utilizzare array di valori:
// INSERIMENTO sicuro
$database->query('INSERT INTO users', [
'name' => $_GET['name'],
'email' => $_GET['email'],
]);
// AGGIORNAMENTO sicuro
$database->query('UPDATE users SET', [
'name' => $_GET['name'],
'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);
Nette Database esegue automaticamente l'escape di tutti i valori passati attraverso le query parametrizzate. Tuttavia, è necessario garantire il corretto tipo di dati dei parametri.
Le chiavi di array non sono un'API sicura
Mentre i valori negli array sono sicuri, lo stesso non si può dire per le chiavi:
// Codice non sicuro: le chiavi possono contenere un'iniezione SQL.
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);
Per i comandi INSERT
e UPDATE
si tratta di una falla di sicurezza critica: un utente malintenzionato
potrebbe inserire o modificare qualsiasi colonna del database. Ad esempio, potrebbe impostare is_admin = 1
o inserire dati arbitrari in colonne sensibili.
Nelle condizioni di WHERE
, questo è ancora più pericoloso perché permette la Enumerazione SQL, una
tecnica per recuperare gradualmente informazioni sul database. Un utente malintenzionato potrebbe tentare di esplorare gli
stipendi dei dipendenti iniettando in $_GET
questo tipo di dati:
$_GET = ['salary >', 100000]; // inizia a determinare le fasce salariali
Il problema principale, tuttavia, è che le condizioni di WHERE
supportano espressioni SQL nelle chiavi:
// Uso legittimo degli operatori nelle chiavi
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5],
]);
// Non sicuro: l'attaccante può iniettare il proprio SQL.
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // permette all'attaccante di ottenere stipendi da amministratore
Si tratta ancora una volta di Iniezione SQL.
Eliminazione delle colonne
Se si desidera consentire agli utenti di scegliere le colonne, utilizzare sempre una whitelist:
// ✅ Elaborazione sicura - solo colonne consentite
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));
$database->query('INSERT INTO users', $values);
Identificatori dinamici
Per i nomi dinamici di tabelle e colonne, utilizzare il segnaposto ?name
:
// ✅ Uso sicuro di identificatori affidabili
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// ❌ UNSAFE - non utilizzare mai l'input dell'utente
$database->query('SELECT ?name FROM users', $_GET['column']);
Il simbolo ?name
deve essere utilizzato solo per i valori affidabili definiti nel codice dell'applicazione. Per
i valori forniti dall'utente, utilizzare nuovamente una whitelist.