Si l'entrée de l'utilisateur est insérée sans modification dans une requête SQL, l'application devient vulnérable à l'[injection SQL][1], comme dans l'exemple suivant :
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
En effet, l'utilisateur peut saisir quelque chose comme valeur' ;); DROP TABLE table;--
, et la requête devient :
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Que peut-on faire pour éviter que cela ne se produise ?
Avertissement déprécié:
L'exemple de code de cette réponse (comme l'exemple de code de la question) utilise l'extension MySQL
de PHP, qui a été dépréciée en PHP 5.5.0 et supprimée entièrement en PHP 7.0.0.
Avertissement de sécurité : Cette réponse n'est pas conforme aux bonnes pratiques de sécurité. [L'échappement est insuffisant pour empêcher l'injection SQL] (https://paragonie.com/blog/2015/05/preventing-sql-injection-in-php-applications-easy-and-definitive-guide), utilisez plutôt des prepared statements. Utilisez la stratégie décrite ci-dessous à vos propres risques. (De plus,
mysql_real_escape_string()
a été supprimé en PHP 7).
Si vous utilisez une version récente de PHP, l'option mysql_real_escape_string
décrite ci-dessous ne sera plus disponible (bien que mysqli::escape_string
soit un équivalent moderne). De nos jours, l'option mysql_real_escape_string
n'a de sens que pour le code hérité sur une ancienne version de PHP.
Vous avez deux options : échapper les caractères spéciaux dans votre unsafe_variable
, ou utiliser une requête paramétrée. Les deux vous protègent des injections SQL. La requête paramétrée est considérée comme la meilleure pratique, mais vous devrez passer à une extension MySQL plus récente en PHP avant de pouvoir l'utiliser.
Nous traiterons d'abord de l'échappement des chaînes de caractères, qui a un impact moindre.
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
Voir aussi les détails de la fonction [mysql_real_escape_string
][1].
Pour utiliser la requête paramétrée, vous devez utiliser [MySQLi][2] plutôt que les fonctions [MySQL][3]. Pour réécrire votre exemple, nous aurions besoin de quelque chose comme ce qui suit.
<?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 fonction clé sur laquelle vous devez vous renseigner est [mysqli::prepare
][4].
De plus, comme d'autres l'ont suggéré, vous trouverez peut-être utile/plus facile de passer à un niveau d'abstraction supérieur avec quelque chose comme [PDO][5].
Veuillez noter que le cas que vous avez demandé est assez simple et que des cas plus complexes peuvent nécessiter des approches plus complexes. En particulier :
Si vous voulez modifier la structure du SQL en fonction des entrées de l'utilisateur, les requêtes paramétrées ne vont pas vous aider, et l'échappement requis n'est pas couvert par mysql_real_escape_string
. Dans ce genre de cas, il est préférable de faire passer l'entrée de l'utilisateur par une liste blanche pour s'assurer que seules les valeurs sûres sont autorisées à passer.
Si vous utilisez des nombres entiers provenant de l'entrée de l'utilisateur dans une condition et adoptez l'approche mysql_real_escape_string
, vous souffrirez du problème décrit par [Polynomial][6] dans les commentaires ci-dessous. Ce cas est plus délicat car les nombres entiers ne sont pas entourés de guillemets. Vous pouvez donc vous en sortir en vérifiant que l'entrée de l'utilisateur ne contient que des chiffres.
Il existe probablement d'autres cas dont je n'ai pas connaissance. Vous trouverez peut-être [ceci][7] une ressource utile sur certains des problèmes les plus subtils que vous pouvez rencontrer.
[1] : http://php.net/mysql_real_escape_string [2] : http://php.net/mysqli [3] : http://php.net/mysql [4] : http://php.net/mysqli.prepare [5] : http://php.net/pdo [6] : https://stackoverflow.com/users/978756/polynomial [7] : http://webappsec.org/projects/articles/091007.txt
Je vous recommande d'utiliser [PDO][1] (PHP Data Objects) pour exécuter des requêtes SQL paramétrées.
Cela permet non seulement de se protéger contre les injections SQL, mais aussi d'accélérer les requêtes.
Et en utilisant PDO plutôt que les fonctions mysql_
, mysqli_
, et pgsql_
, vous rendez votre application un peu plus abstraite de la base de données, dans le cas rare où vous devriez changer de fournisseur de base de données.
Vous pourriez faire quelque chose de basique comme ça :
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Cela ne résoudra pas tous les problèmes, mais c’est un très bon point de départ. J'ai laissé de côté des éléments évidents comme la vérification de l'existence de la variable, son format (chiffres, lettres, etc.).