mysql注入大全(二)
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)
)