Eviter les injections SQL Comment bloquer les injections SQL

Il existe une stratégie générale de défense contre les injections, qu'elles soient SQL ou autre : il s'agit de la validation des données en entrée et de la protection en sortie.

Dans les exemples présentés précédemment, le script a omis de faire les deux : les données d'entrée, dans la variable $_POST, sont utilisées sans être validées, alors qu'elles devraient l'être dès le début du script.

Il en va de même pour les données de sortie : elles sont utilisées dans la requête sans avoir été rendues neutres pour le langage SQL.

De manière tactique, il existe plusieurs techniques pour neutraliser les variables qui entrent dans la composition d'une requête SQL.


Assurer la protection des valeurs SQL

La protection des valeurs, appelée aussi échappement par anglicisme, est la première tactique. Elle est universellement disponible, même avec de vieilles versions de PHP.

Au lieu d'utiliser la valeur de $_POST directement dans la commande, on la passe à la fonction mysqli_real_escape_string(), qui neutralise tous les caractères susceptibles de perturber le fonctionnement d'une requête, en ajoutant une barre oblique inverse juste devant ces caractères.

$_POST ['passe'] = " ' or (1) = '1 "
$mysql_passe = mysqli_real_escape_string($mid, $_POST['passe']) ;


Après ce traitement, $mysql_passe contient ceci :

 \' or (1) = \'1


Les guillemets sont désormais précédés de barres obliques inverses : on dit qu'ils sont "neutralisés". Leur valeur SQL est maintenant la même que leur valeur littérale. Ils ont perdu la signification spéciale que leur confère le langage SQL.

Toutefois, pour bien assurer la neutralisation de l'ensemble, il faut que la chaîne fournie soit placée entre guillemets dans la requête. Les types de guillemets, qu'ils soient simples ou doubles, n'ont pas d'importance, car mysqli_real_escape_string() sait aussi neutraliser les guillemets doubles.

Cependant, notez que les parenthèses n'ont pas été protégées. Elles introduisent une sous-sélection et peuvent conduire à un déni de service. Toutefois, si les guillemets ont bien été neutralisés, elles garderont leur valeur littérale et ne poseront pas de problème. Il est donc important de bien utiliser les guillemets d'encadrement dans toutes les requêtes SQL.

Or, en SQL, il est possible d'éviter les guillemets lorsqu'on manipule des nombres. Comme la fonction mysqli_real_escape_string() laisse passer les parenthèses, on retrouve une vulnérabilité par injection de sous-requête.

Si vous devez manipuler des nombres dans une requête, il est recommandé de forcer le type à un format numérique dans PHP, avec + 0, ou bien avec MySQL, en laissant les guillemets et en ajoutant + 0 dans la requête :

<?php
$_POST['id'] = " ' or (1) = '1 " + 0 ;
$requete = "SELECT COUNT(*) FROM utilisateurs
WHERE id = '".
mysqli_real_escape_string($mid, $_POST['id'])."' + 0 ";
?>


En utilisant ce principe de transtypage forcé, voici une astuce pour les mots de passe, ou les valeurs de hashage en général. Les mots de passe ne doivent jamais être stockés en clair ; ils sont toujours signés avec une fonction de hashage, comme MD5 ou SHA. Ces deux fonctions, disponibles simultanément en PHP et MySQL, produisent des chaînes de caractères qui ne contiennent que des chiffres et les lettres allant de A à F. Aucun de ces
caractères n'a de signification particulière en SQL, ce qui permet de les utiliser en toute sécurité.

Ainsi, avant de rechercher une correspondance de mot de passe, il est recommandé de le passer à MD5 (ou équivalent), afin d'obtenir une valeur sécurisée pour le serveur SQL. De cette manière, vos utilisateurs pourront utiliser les caractères qu'ils souhaitent dans leur mot de passe, et vous ne compromettrez pas la sécurité de votre système d'identification.

<?php
$passe = md5('sel'. $_POST['passe'].'poivre');
// $passe vaut d7b72c8f5eaa6ddda7d9beb25417a698 ou similaire
$requete = "SELECT COUNT(*) FROM utilisateurs
WHERE login = '".
mysqli_real_escape_string($mid, $_POST ['id'])
."' AND password = '$passe'";
?>