Les injections SQL

Auteur : palkeo
Créé le : le 06/09/2008 à 15:40
Visualisations : 6783
Ce tutoriel est composé des parties suivantes :

Introduction

Si, sur votre site, vous utilisez le SQL pour stocker vos données, ce tutoriel vous concerne.
En effet, lorsque votre script envoie des requêtes à votre SGBD, il est possible d'injecter du code pour détourner la requête originale !

Présentation de l'injection SQL

Prenons l'exemple du script PHP suivant :
Code : PHP
$pseudo = $_GET['pseudo']; // On récupère le pseudo
$password = $_GET['password']; // On récupère le Mot de passe
mysql_connect('Serveur', 'NomDUtilisateur', 'MotDePasse'); // Connexion à MySQL
mysql_select_db('LivreDOr'); // Sélection de la base de données
$requete = mysql_query("SELECT * FROM membres WHERE pseudo = '$pseudo' AND password = '$password'");
if(mysql_num_rows($requete) == 1)
 echo 'Bonjour !';
else
 echo 'Login incorrect !';
Donc, le script va récupérer le pseudo et le mot de passe, puis se connecte à MySQL, sélectionne la base de données "LivreDOr", et regarde si il existe dans la table "membres" un utilisateur ayant pour pseudo le pseudo envoyé, et pour mot de passe le mot de passe donné.
Si un tel utilisateur existe, il affiche "Bonjour !", sinon, il affiche "Login incorrect !".

Ainsi, si on donne comme pseudo "superPseudo" et comme mot de passe "superMotDePasse", là requête envoyée à MySQL donnera ceci :
Code : MySQL
SELECT * FROM membres WHERE pseudo = 'superPseudo' AND password = 'superMotDePasse'
Mais que se passe t'il si le pseudo ou le mot de passe contiennent un guillemet (') ?
En effet, c'est là qu'est la source du problème : Si un utilisateur insère des guillemets, il pourra modifier la requête. O_o

Par exemple, si le pseudo envoyé est "administrateur" et le mot de passe "faille' OR 1=1 #"
La requête sera transformée en :
Code : MySQL
SELECT * FROM membres WHERE pseudo = 'administrateur' AND password = 'faille' OR 1=1 #'
Et cette requête est valide !
MySQL va prendre tous les champs ayant pour pseudo "administrateur" et, soit quand le mot de passe est "faille", soit quand 1=1.
Si vous avez quelques notions en mathématiques, vous devez savoir que 1 est égal à 1 :o)
Ainsi, MySQL prendra le membre ayant pour pseudo "administrateur", quel que soit son mot de passe :/
Note : le # permet de dire à MySQL que ce qui suit est un commentaire, et permet donc d'ignorer le guillemet censé fermer la chaîne contenant le mot de passe.

Ainsi, par cette faille, il est possible d'utiliser les guillemets pour injecter du code MySQL.

Notez qu'il est aussi possible de faire une injection SQL sans guillemets :
Imaginez un script qui récupère le contenu d'un message ayant un ID numérique passé au script en paramètre. Si, par exemple, l'ID est 32, on aurait cette requête :
Code : MySQL
SELECT texte FROM message WHERE id = 32
Mais, là aussi, un utilisateur peut injecter du code, en remplaçant l'ID numérique par du SQL...

Exploitation

Tout d'abord, pour savoir si un site est vulnérable à cette faille, il suffit d'essayer d'aller dans quelques formulaires, et de mettre dans tous les champs un texte contenant des guillemets.
Vous pouvez aussi essayer de mettre du texte, ou encore des guillemets, à la place d'un nombre attendu par un script dans l'URL.
Et ensuite, vous n'avez qu'a voir comment le site réagit, si vous obtenez un joli message d'erreur du SGBD, il y a des chances que le site soit vulnérable.

Le moyen utilisé pour "découvrir" un maximum de choses sur la requête ainsi que le script utilisé, puis exploiter le tout, est vraiment très intéressant, mais sort un peu du contexte de ce tutoriel, c'est pourquoi je ne l'expliquerais pas.
Si vous êtes intéressé, je vous recommande vivement de rechercher "blind SQL injection", ou encore "blindfolded SQL injection" sur le Web.

Protection

Pour vous protéger, il n'y a qu'une seule chose d'efficace : NEVER TRUST USER INPUT !
Ce qui peut se traduire en français par "ne jamais faire confiance à l'utilisateur"

Donc, pour chaque requête SQL, vous devez agir en fonction du type des variables qui seront inclues dans la requête.

Si vous incluez un nombre :
Utilisez la fonction is_int() pour savoir si c'est un nombre entier, ou is_float() si vous attendez un nombre à virgule.
Ainsi, si le paramètre attendu n'est pas un nombre, vous pouvez interrompre le script.
Vous pouvez aussi utiliser la fonction intval() qui transformera le paramètre attendu en nombre entier, quel que soit sa valeur, et vous permettra donc d'éviter les injections SQL.

Si vous incluez une chaîne de caractères :
Si vous incluez une chaîne de caractères, vous pouvez utiliser la fonction addslashes(), ou mysql_real_escape_string() (dans le cas de MySQL, sinon, il existe des équivalents)
Ces fonctions vont se charger "d'échapper" des caractères tels que ' en les remplaçant par \', par exemple. Ainsi, grâce à l'antislash, votre SGBD sera que la chaîne de caractères continue, et que le guillemet n'indique pas la fin de la chaîne.
Mais j'utilise quoi, moi, addslashes() ou mysql_real_escape_string() ?
Personnellement, je vous conseillerais mysql_real_escape_string().
Pourquoi ?
Car addslashes() a été écrit par les développeurs PHP, et mysql_real_escape_string() a été écrit par les développeurs de MySQL.
Or, cette fonction concerne l'interface avec MySQL, et si il y a des développeurs qui doivent s'occuper de ça, c'est bien les développeurs MySQL.
D'ailleurs, il est même possible de faire une injection SQL sur un script utilisant addslashes().
Ne vous inquiétez pas, ça ne risque pas d'arriver sur votre site, même si vous utilisez addslashes(), mais ça prouve bien qu'il vaut mieux utiliser mysql_real_escape_string().
Si vous voulez en savoir plus, lisez cet article (attention, c'est en anglais).

Si on reprend notre script PHP d'exemple, voici comment il aurait du être :
Code : PHP
mysql_connect('Serveur', 'NomDUtilisateur', 'MotDePasse'); // Connexion à MySQL
mysql_select_db('LivreDOr'); // Sélection de la base de données
// mysql_real_escape_string() fonctionne seulement quand on est connecté à MySQL
$pseudo = mysql_real_escape_string($_GET['pseudo']); // On récupère le pseudo, puis on "l'échappe"
$password = mysql_real_escape_string($_GET['password']); // On récupère le Mot de passe, puis on "l'échappe"
$requete = mysql_query("SELECT * FROM membres WHERE pseudo = '$pseudo' AND password = '$password'");
if(mysql_num_rows($requete) == 1)
 echo 'Bonjour !';
else
 echo 'Login incorrect !';


Les requêtes préparées :
Il existe aussi un autre moyen de se protéger contre les injections SQL : les requêtes préparées.

Le cas des magic quotes

Vous avez peut-être déjà eu la désagréable surprise d'avoir des \' à la place des ' dans tous les messages de votre site.
Le responsable, c'est lui : magic_quotes_gpc, aussi appelé du nom terrible de "guillemets magiques" !

C'est une directive de configuration de PHP, qui "échappe" automatiquement toutes les données $_GET, $_POST et $_COOKIE, en ajoutant des antislash.

Le problème, c'est qu'il n'y à pas forcément besoin d'échapper toutes les données.
Par exemple, si vous avez un formulaire d'envoi de mail, tous les guillemets seront remplacés par des \' :/
En plus, cette option va aussi entraîner une baisse des performances de votre script, puisque toutes les variables (même celles qui ne seront pas insérées dans votre SGBD) seront échappées.

C'est pourquoi cette directive de configuration a été supprimée depuis PHP 6.0.0, et est déconseillée.

Pour contourner ce problème, vous avez deux solutions :
1) La désactivation complète :
Cette solution ne marche que si vous avez accès au fichier de configuration de PHP (php.ini)
pour cela, il suffit d'éditer le fichier de configuration de PHP (chez moi, sur mon serveur GNU/Linux, il est situé dans /etc/php/apache2-php5/php.ini).
Ensuite, vous devez modifier la ligne suivante :
Code : PHP.ini
magic_quotes_gpc = On
en
Code : PHP.ini
magic_quotes_gpc = Off

2) Technique d'annulation des effets des guillemets magiques :
Cette technique consiste à, dans chaque page, enlever les antislashes de protection mis automatiquements par PHP.
Pour celà, il suffit simplement de rajouter ceci au début de tous vos fichiers PHP :
Code : PHP
if(get_magic_quotes_gpc())
{
 $_GET = array_map('stripslashes',$_GET);
 $_POST = array_map('stripslashes',$_POST);
 $_COOKIE = array_map('stripslashes',$_COOKIE);
}
Ce code s'assure que les guillemets magiques sont bien activés, puis retire les antislashes de toutes les variables reçues par le script.

Conclusion

Comme nous venons de le voir, l'injection SQL peut donc être une vulnérabilité très dangereuse pour votre site web, c'est pourquoi vous devez redoubler d'attention quand vous exécutez des requêtes.