Source: http://underground54.serveurperso.com
Auteur: Brayan (brayan@underground54.tk)
Rédigé le: 13/08/2006
Introduction:
Ce papier décrit brièvement des injections SQL particulières sans aller dans les détails ni sans expliquer les principes généraux de l’attaque par SQL injection. Ainsi, pour l’aborder, je vous conseil vivement de connaître php/MySQL ainsi que les attaques par SQL injection.
Ceux qui connaissent ces attaques et qui désirent récupérer la source d’un fichier en exploitant un MySQL SELECT vulnérable, savent qu’une méthode consiste à charger le fichier à l’aide de LOAD_FILE() et à le copier à l’aide de INTO OUTFILE/DUMFILE. Vous savez aussi que, malheureusement, les conditions sont rarement favorables à l’utilisation de cette méthode ce qui restreint énormément son champ d’application.
En plus de nécessiter du privilège FILE, les guillemets obligatoires à l’utilisation de OUTFILE/DUMPFILE ne devront pas être échappés (par un magic_quotes_* ou un addslashes()) ce qui est pourtant le cas dans 97% des cas.
Si dans 3% des cas vos guillemets simples ou doubles ne seront pas échappés, il devient très rare de disposer en même temps du privilège FILE, ce qui réduit à néant vos chances de récupérer la source d’un fichier à l’aide de la “vieille méthode”.
Explication:
Dans ce nouveau procédé, le fichier est bien chargé à l’aide d’un LOAD_FILE() mais ne sera pas copié puisqu’on sait que cela pose des problèmes liés aux guillemets. Au lieu de cela, nous allons faire une suite de tests afin de récupérer quelques informations sur le fichier et surtout accéder à son contenu par brutforce.
Voici grossièrement à quoi peuvent se résumer ces tests: “Est ce que l’octet X du fichier Y est un Z, oui? non?”
Pour se faire nous utiliserons principalement deux fonctions, LOAD_FILE() qui prend comme unique argument le fichier à lire (il doit être présent sur le serveur qui exécute MySQL, vous devez avoir les droits en lecture, et le fichier doit être plus petit que max_allowed_packet) et la fonction SUBSTRING() qui retourne une sous-chaîne à partir d’une chaîne de caractères. Ainsi SUBSTRING(str,pos,len) retourne une chaîne de len caractères de long de la chaîne str, à partir de la position pos (le troisième argument est optionnel).
En combinant ces deux fonctions il est alors possible de reconstituer petit à petit le contenu d’un fichier sans utiliser OUTFILE/DUMPFILE.
Puisqu’un bon exemple vaut parfois mieux qu’un long discours, en voici un premier:
SUBSTRING(LOAD_FILE("/home/site/fichier"),1,1) = "<"
Ici on a voulu vérifier si le premier octet de “/home/site/fichier” est un “<”
Il s’agit ensuite de vérifier si la retourne vraie ou faux. En rajoutant un AND à votre requête vous pouvez vérifier si le retour est le même ou non et ainsi si votre condition est vraie ou fausse.
Je vous fais un dessin, le code suivant permet d’afficher l’email en fonction du membre.
SELECT email FROM membre WHERE user=$variable_membre_non_filtree
Voyons ce que ça donne lorsque le code est exécuté:
brayan@tux ~ $ GET 'http://serveur/fichier_vulnerable.php?variable_membre_non_filtree=Brayan' email:brayan@underground54.tk brayan@tux ~ $ GET 'http://serveur/fichier_vulnerable.php?variable_membre_non_filtree=Brayan \ AND condition_vraie' email:brayan@underground54.tk brayan@tux ~ $ GET 'http://serveur/fichier_vulnerable.php?variable_membre_non_filtree=Brayan \ AND condition_fausse' email:
Donc si la requête est vraie l’email sera affiché, dans le cas contraire, on sait que le premier octet de notre fichier n’est pas ‘<’.
Mise en pratique:
Il nous suffit de tester si la condition est vraie avec différents caractères (préalablement définis dans un charset) et noter celui pour lequel la condition est vraie, puis passer à l’octet suivant du fichier et ainsi dessuite (oui c’est long est fastidieux, c’est pour cela que je vous conseille d’utiliser mon script, de l’améliorer ou de vous en coder un).
Mais ce n’est pas tout, par la même méthode vous pouvez aussi vérifier l’existence d’un fichier, déterminer sa taille… (je vous renvoie à votre imagination foisonnante).
Le fichier existe?:
Ici il serait judicieux d’utiliser la fonction IF(), elle prend trois arguments comme on peut le voir dans le manuel de référence de MySQL:
IF(expr1,expr2,expr3) Si l’argument expr1 vaut TRUE (expr1 <> 0 et expr1 <> NULL) alors la fonction IF() retourne l’argument expr2, sinon, elle retourne l’argument expr3. La fonction IF() retourne une valeur numérique ou une chaîne de caractères, suivant le contexte d’utilisation:
mysql> SELECT IF(1>2,2,3); -> 3 mysql> SELECT IF(1<2,'oui','non'); -> 'oui'
Voici ce que vous pourriez utiliser pour vérifier l’existence du fichier “/home/site/fichier”:
AND (IF((LOAD_FILE("/home/site/fichier")<>''),1,0)) = 1
Littéralement on a: si le chargement de “/home/site/fichier” ne retourne pas une chaîne vide, retourner un 1 sinon retourner un 0.
Si vous désirez connaître la taille d’un fichier combinez LENGTH() avec SUBSTRING() et LOAD_FILE(): “LENGTH(str) retourne la taille de la chaîne str, mesurée en octets.”
Brutforcer la taille du fichier:
AND SUBSTRING(LENGTH(LOAD_FILE("/home/site/fichier")),$pos,1) = "$numerique"
Si vous ne comprenez pas référez vous au manuel de MySQL et/ou relisez plus haut.
À présent il faut se débarrasser de tous les guillemets, optez pour des techniques d’évasions simples utilisées pour mettre à mal les *IDS/IPS à savoir: utiliser plusieurs CHAR() séparés par des virgules ou la forme hexadécimal pour passer le fichier à la fonction LOAD_FILE() utilisez également CHAR() pour comparer un à un les octet du fichier. On utilisera ‘= CHAR(60)’ plutôt que ‘= “<”‘, je vous laisse vous référer au manuel de MySQL pour cette fonction. Pour plus de prudence je vous invite également à utiliser ‘%28′ et ‘%29′ pour remplacer respectivement ‘(’ et ‘)’ et des ‘+’ pour remplacer les espaces. Ces petites astuces pourraient vous éviter bien des désagréments par la suite.
En résumé la chaîne suivante:
AND SUBSTRING(LOAD_FILE("/home/site/fichier"),1,1) = "<"
deviendrait:
+AND+SUBSTRING%28LOAD_FILE%280x2f686f6d652f736974652f66696368696572%29,1,1%29+=+CHAR%2860%29
Le script:
Il s’agit d’un script bash permettant d’exploiter les techniques précédemment expliquées. Pourquoi du bash? Tout simplement parce qu’en premier lieu ce script était dédié à une utilisation personnelle, j’avais besoin d’automatiser ce type d’attaque rapidement, il ne prenait alors que 10 lignes et était opérationnel au bout de 5 minutes. En C ça aurait été une autre affaire et je ne connais pas assez bien perl pour faire quelque chose de correct. Par la suite j’ai trouvé que le bash était très bien pour un article puisque c’est un langage très accessible.
Je tiens à préciser que je suis loin d’être un fin codeur (tous langages confondus) alors dans la mesure du possible, optimisez ce script ou codez vous en un (vos conseils et critiques sont les bienvenus).
Pour faire fonctionner ce script vous aurez besoin d’un interpréteur bash, de la libwww-perl et du binaire awk.
Ne tardons plus le voici: SQL-Read.sh (–help pour voir la liste des commandes).