Mysql
 sql >> Database >  >> RDS >> Mysql

Come posso prevenire l'iniezione SQL in PHP?

Il corretto un modo per evitare attacchi SQL injection, indipendentemente dal database utilizzato, è separare i dati da SQL , in modo che i dati rimangano tali e non verranno mai interpretati come comandi dal parser SQL. È possibile creare istruzioni SQL con parti di dati formattate correttamente, ma se non lo fai completamente comprendere i dettagli, dovresti sempre utilizzare istruzioni preparate e query parametrizzate. Si tratta di istruzioni SQL inviate e analizzate dal server di database separatamente da qualsiasi parametro. In questo modo è impossibile per un utente malintenzionato iniettare SQL dannoso.

Fondamentalmente hai due opzioni per raggiungere questo obiettivo:

  1. Utilizzo di DOP (per qualsiasi driver di database supportato):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
  2. Utilizzo di MySQLi (per MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
     $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
     $stmt->execute();
    
     $result = $stmt->get_result();
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    

Se ti stai connettendo a un database diverso da MySQL, c'è una seconda opzione specifica del driver a cui puoi fare riferimento (ad esempio, pg_prepare() e pg_execute() per PostgreSQL). DOP è l'opzione universale.

Impostazione corretta della connessione

Tieni presente che quando utilizzi DOP per accedere a un database MySQL reale le istruzioni preparate sono non utilizzate per impostazione predefinita . Per risolvere questo problema devi disabilitare l'emulazione delle istruzioni preparate. Un esempio di creazione di una connessione utilizzando PDO è:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Nell'esempio sopra la modalità errore non è strettamente necessaria, ma ​​si consiglia di aggiungerla . In questo modo lo script non si fermerà con un Fatal Error quando qualcosa va storto. E offre allo sviluppatore la possibilità di catch qualsiasi errore che sia throw n come PDOException s.

Che cos'è obbligatorio , tuttavia, è il primo setAttribute() line, che dice a PDO di disabilitare le istruzioni preparate emulate e di utilizzare real dichiarazioni preparate. Ciò assicura che l'istruzione e i valori non vengano analizzati da PHP prima di inviarli al server MySQL (non dando a un possibile aggressore la possibilità di iniettare SQL dannoso).

Sebbene tu possa impostare il charset nelle opzioni del costruttore, è importante notare che le versioni "precedenti" di PHP (prima della 5.3.6) ignora silenziosamente il parametro charset nel DSN.

Spiegazione

L'istruzione SQL che passi a prepare viene analizzato e compilato dal server di database. Specificando i parametri (o un ? o un parametro denominato come :name nell'esempio sopra) dici al motore di database dove vuoi filtrare. Quindi quando chiami execute , l'istruzione preparata viene combinata con i valori dei parametri specificati.

La cosa importante qui è che i valori dei parametri siano combinati con l'istruzione compilata, non con una stringa SQL. SQL injection funziona inducendo lo script a includere stringhe dannose quando crea SQL da inviare al database. Quindi, inviando l'SQL effettivo separatamente dai parametri, limiti il ​​rischio di finire con qualcosa che non volevi.

Tutti i parametri inviati quando si utilizza un'istruzione preparata verranno semplicemente trattati come stringhe (sebbene il motore di database possa eseguire alcune ottimizzazioni, quindi anche i parametri potrebbero finire come numeri, ovviamente). Nell'esempio sopra, se il $name la variabile contiene 'Sarah'; DELETE FROM employees il risultato sarebbe semplicemente una ricerca per la stringa "'Sarah'; DELETE FROM employees" e non ti ritroverai con una tabella vuota .

Un altro vantaggio dell'utilizzo di istruzioni preparate è che se esegui la stessa istruzione più volte nella stessa sessione, verrà analizzata e compilata solo una volta, offrendoti alcuni guadagni di velocità.

Oh, e poiché hai chiesto come farlo per un inserto, ecco un esempio (usando PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

Le istruzioni preparate possono essere utilizzate per query dinamiche?

Sebbene sia ancora possibile utilizzare istruzioni preparate per i parametri della query, la struttura della query dinamica stessa non può essere parametrizzata e alcune funzioni della query non possono essere parametrizzate.

Per questi scenari specifici, la cosa migliore da fare è utilizzare un filtro whitelist che limiti i possibili valori.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}