Se l'input dell'utente viene inserito senza modifiche in una query SQL, allora l'applicazione diventa vulnerabile a SQL injection, come nel seguente esempio:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
Questo perché l'utente può inserire qualcosa come valore'); DROP TABLE table;--
, e la query diventa:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Cosa si può fare per evitare che questo accada?
Avvertenza deprecata:
Il codice di esempio di questa risposta (come quello della domanda) usa l'estensione MySQL
di PHP, che è stata deprecata in PHP 5.5.0 e rimossa completamente in PHP 7.0.0.
Avviso di sicurezza: Questa risposta non è in linea con le migliori pratiche di sicurezza. L'escape è inadeguato a prevenire l'iniezione SQL, usa invece prepared statements. Utilizzare la strategia descritta di seguito a proprio rischio e pericolo. (Inoltre, mysql_real_escape_string()
è stato rimosso in PHP 7.)
Se state usando una versione recente di PHP, l'opzione mysql_real_escape_string
non sarà più disponibile (anche se mysqli::escape_string
è un equivalente moderno). Al giorno d'oggi l'opzione mysql_real_escape_string
avrebbe senso solo per il codice legacy su una vecchia versione di PHP.
Hai due opzioni - l'escape dei caratteri speciali nella tua unsafe_variable
, o usare una query parametrata. Entrambi ti proteggeranno dall'SQL injection. La query parametrata è considerata la pratica migliore, ma richiederà il passaggio ad una nuova estensione MySQL in PHP prima di poterla usare.
Tratteremo prima l'escaping delle stringhe a basso impatto.
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
Vedere anche i dettagli della funzione mysql_real_escape_string
.
Per utilizzare la query parametrizzata, è necessario utilizzare MySQLi piuttosto che le funzioni MySQL. Per riscrivere il tuo esempio, avremmo bisogno di qualcosa come il seguente.
<?php
$mysqli = new mysqli("server", "username", "password", "database_name");
// TODO - Check that connection was successful.
$unsafe_variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// TODO check that $stmt creation succeeded
// "s" means the database expects a string
$stmt->bind_param("s", $unsafe_variable);
$stmt->execute();
$stmt->close();
$mysqli->close();
?>
La funzione chiave su cui vorrai leggere sarà mysqli::prepare
.
Inoltre, come altri hanno suggerito, potreste trovare utile/facile salire di un livello di astrazione con qualcosa come PDO.
Si prega di notare che il caso che hai chiesto è abbastanza semplice e che casi più complessi possono richiedere approcci più complessi. In particolare:
mysql_real_escape_string
. In questo tipo di caso, sarebbe meglio far passare l'input dell'utente attraverso una whitelist per assicurarsi che solo i valori sicuri siano ammessi.mysql_real_escape_string
, soffrirete del problema descritto da Polynomial nei commenti qui sotto. Questo caso è più complicato perché gli interi non sarebbero circondati da virgolette, quindi si potrebbe affrontare con la convalida che l'input dell'utente contenga solo cifre.Vi consiglio di usare PDO (PHP Data Objects) per eseguire delle query SQL parametrizzate.
Non solo questo protegge dall'iniezione SQL, ma velocizza anche le query.
E usando PDO piuttosto che le funzioni mysql_
, mysqli_
, e pgsql_
, rendi la tua applicazione un po' più astratta dal database, nella rara eventualità che tu debba cambiare fornitore di database.
Si potrebbe fare qualcosa di base come questo:
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Questo non risolverà tutti i problemi, ma è un ottimo punto di partenza. Ho lasciato fuori elementi ovvi come il controllo dell'esistenza della variabile, il formato (numeri, lettere, ecc.).