Injections SQL avancées


HTTP Splitting >>


   Dans l'article précédent d'introduction aux injections SQL, nous avons entrevu les possibilités offertes par l'introduction de données dynamiques dans les requêtes SQL sans nettoyage des entrées. En réalité, bien qu'il ne possède pas la puissance de Turing, le langage SQL offre un panel large de possibilités permettant d'envisager des compromissions potentiellement bien plus sévères qu'un simple bypass de requête. C'est ce que nous allons découvrir dans cette section. J'ai essentiellement utilisé des exemples spécifiques à MySQL qui se transposent en général aux autres DBMS de manière relativement directe.

Injections avancées et paramètres numériques
   Nous avions en premier lieu expliqué les injections sur les chaînes de caractères, puisque les exemples de bypass de mot de passe sont traditionnellement le b-a-ba de l'injection SQL. De ce fait, on entends souvent "de toute façon, pour supprimer les injections SQL, c'est facile, il suffit d'échapper les simple et double quotes. Ce qu'il n'est pas toujours facile de voir est que les injections sur les paramètres numériques sont bien plus fréquentes et bien plus dangereuses.
Imaginons le bout de code suivant :
    $id_service = $_POST["idservice"];
    $resp = mysql_query("SELECT nom,prenom from employes WHERE idservice=".$idservice.";");

    /* Affichage des résultats */
En effet, on se rend compte qu'on peut aisément modifier les résultats (par exemple idservice=1 OR 1=1) qui afficherait tous les noms et prénoms de tous les services et ce sans la moindre quote. Je vous l'accorde, ce n'est pas forcément très intéressant (quoique dans l'optique d'un pentesting, avoir des noms/prénoms peut toujours être un plus quand on en vient à bruteforcer des logins).
Vous l'avez compris, en effectuant des requêtes plus intelligentes on peut aller beaucoup plus loin, par exemple :
    SELECT nom,prenom from employes WHERE idservice=1 OR 1=1 AND no_securite_sociale < 200000000000000 ;
    SELECT nom,prenom from employes WHERE idservice=1 AND 1=1 UNION SELECT login as nom,password as prenom FROM users;
    SELECT nom,prenom from employes WHERE idservice=1 AND 1=1 UNION SELECT table_name as nom,column_name as prenom FROM INFORMATION_SCHEMA.COLUMNS;
On commence à comprendre qu'il est possible d'aller loin avec une simple injection SQL (ces requêtes ne sont bien sûr pas spécifiques du fait que le paramètre soit numérique). La première permet de lister tous les employés masculins, tandis que la deuxième permet d'afficher sur la page tous les logins/mots de passe d'une autre table et, encore mieux, la troisième permet de lister toutes les tables et leurs colonnes (pour une base de données MySQL, mais il y a des équivalents pour les autres DBMS).
Il faut aussi préciser qu'il existe beaucoup de fonctions de manipulation des chaînes et des nombres, permettant d'utiliser des chaînes de caractères dans une requête sans utiliser de quotes (en l'écrivant en hexadecimal ou en ASCII par exemple).

Requêtes doublées
   Avant d'avancer plus loin, je souhaite parler brièvement des requêtes doublées. En effet, un bon nombre de développeurs Web ont commencé leur apprentissage sur des langages comme PHP qui ne permettent pas de doubler les requêtes ou Java qui restreint les requêtes possibles par l'utilisation de méthodes distinctes pour différents types de requêtes. Ceci dit, dans d'autres langages qui "ceinturent" moins le programmeur (ce qui est tout de même un certain but de Java) comme ASP, il est possible d'effectuer plusieurs requêtes dans une même connexion à la base de données (il est ainsi possible de passer un script SQL complet au DBMS). Ainsi, en prenant le même bout de code que précédemment mais transposé en ASP, on obtient des requêtes beaucoup plus dangereuses :
    SELECT nom,prenom from employes WHERE idservice=1; DELETE FROM employes WHERE 1=1;
    SELECT nom,prenom from employes WHERE idservice=1; ALTER news SET content="Hacked by skiddie";
Ce qui devient tout de suite plus dangereux puisqu'il est possible d'utiliser des requêtes bien plus destructrices qu'un simple SELECT. On a déjà vu des bases de données pédagogiques utilisées par plusieurs sites de l'éducation nationale se voir injecter des codes javascript malveillants de cette façon, codes qui redirigaient ensuite les utilisateurs vers d'autres sites ou qui faisaient crasher leurs navigateurs (sans doute en tentant divers exploits mémoire).

Injection partiellement aveugles
   De manière plus commune, on retrouve les injections partiellement aveugles (Partially Blind SQL Injections) qui permettent d'extraire des enregistrements de la base de données sans avoir un affichage aussi amical que dans l'exemple précédent. L'exemple typique serait celui d'un site de news :
    $id_news = $_POST["idnews"];
    $resp = mysql_query("SELECT title,content from news WHERE id=".$idnews.";");
    /* Affichage de l'article */
En effet, l'affichage effectué sera celui d'un élément et d'un seul. Si l'on ne connait pas la structure de la requête ou de la base de données sousjacente, il ne sera pas possible de dumper la base de données aussi efficacement (ou de manière très laborieuse avec l'utilisation de LIMIT). Ceci dit, cette requête nous offre une information cruciale : savoir si la requête est vraie ou non. Ainsi, en injectant 1 AND 1=1 et 1 AND 1=2, la page affichée sera différente (affichage d'un article par défaut ou d'une erreur dans le second cas). Or, l'informatique étant constituée de 0 et de 1, il reste possible de divulguer la base de données par itérations successives :
    SELECT title,content from news WHERE idservice=1 AND 1=0 UNION SELECT 1,2 FROM users WHERE login=char(97,100,109,105,110) AND substr(password,0,1) > char(97);
    SELECT title,content from news WHERE idservice=1 AND 1=0 UNION SELECT 1,2 FROM INFORMATION_SCHEMA.TABLES WHERE substr(table_name,5,1) > char(101));
    SELECT title,content from news WHERE idservice=1 ORDER BY 3;
Dans cette exemple, on vérifie si le premier caractère du mot de passe de l'utilisateur admin (codé en ASCII pour l'occasion) est plus grand que 'a'. Selon l'affichage (soit une page avec 1 comme titre et 2 comme contenu, soit une page d'erreur article inexistant), on peut continuer (passer à la lettre b pour le premier caractère ou passer au second caractère). On a ensuite un exemple similaire sur INFORMATION_SCHEMA. La troisième montre comment il est possible de déterminer le nombre de colonnes dans la requête sur MySQL (ORDER BY 2 ordonnera les données selon la deuxième colonne (ici content) et sera un succès tandis que ORDER BY 3 échouera), ce qui nous permet d'effectuer ensuite des UNION adaptés.


Injection aveugles
   Parfois, même si cela est plus rare et relève plutôt en général d'une singularité de programmation ou de filtres mal conçus, il n'est pas possible d'apercevoir un quelconque résultat d'injection à l'écran. Dans ce cas, on peut avoir recours à diverses techniques, parmi lesquelles ma préféré : la Blind Injection Timing Attack. Le but dans ce cas est de se ramener à une injection partiellement aveugle, dans le sens où l'on veut pouvoir déterminer si une requête est vraie ou fausse. Pour ce faire, on s'arrange pour observer le temps de réponse de la requête et faire en sorte qu'une requête fausse soit normale et qu'une requête vraie soit assez lente pour qu'on puisse le remarquer (ou inversement bien sûr). Certains DBMS comme SQL Server intègrent directement des commandes de ce type (delay). Dans le cas de MySQL, ce n'est pas le cas, mais vous vous en doutez, il est toujours possible de s'arranger :
    SELECT boumboum FROM blabla WHERE badparam=-1 UNION SELECT IF(substr(passwd,0,1) > char(97), 1, benchmark(200000,md5(char(97)))) FROM admins WHERE id=1;
Classe n'est-ce pas ? On utilise donc l'instruction de branchement conditionnel IF(expression, si vraie, si fausse). Dans le cas où la requête est fausse (donc qu'on a trouvé le bon caractère a priori), on effectue 200 000 hashages du caractère 'a' en md5, ce qui prend du temps (vous l'aurez deviné, la fonction benchmark permet de répéter x fois une action). Ainsi, une requête ralentie prendra environ 1,20 secondes en local tandis qu'une requête normale serait quasiment instantanée (bien sûr, il faut augmenter un peu le nombre d'itérations lors d'une utilisation à distance pour pouvoir éponger les aléas du réseau). On sait donc différencier une requête vraie d'une requête fausse et il est possible d'utiliser le même mécanisme itératifs que pour les injections partiellement aveugles pour dumper la base de données. Pas si évident me direz-vous, mais des outils comme sqlmap sont tout à fait familiers avec ce genre de mécanismes et savent les automatiser.


Manipulation de fichiers
   Et oui, SQL n'a pas fini de nous étonner, il est même possible d'y manipuler des fichiers ! En effet, lorsque le privilège FILES sous MySQL est accordé à l'utilisateur (ce qui est par exemple le cas pour les nombreux sites qui utilisent les comptes root), il est possible d'utiliser les droits en lecture et en écriture accordés à l'utilisateur mysql (du système). Mais alors, ça devient grave là non ???
    SELECT boumboum FROM blabla WHERE badparam=-1 UNION SELECT 'Hacked !' INTO OUTFILE '/var/www/target/index.php' ;
    SELECT boumboum FROM blabla WHERE badparam=-1 UNION SELECT '<? mon script shell ?>' INTO OUTFILE '/var/www/target/shell.php' ;
    SELECT boumboum FROM blabla WHERE badparam=-1 UNION SELECT loadfile('/etc/passwd') as boumboum;
    SELECT boumboum FROM blabla WHERE badparam=-1 UNION SELECT loadfile('/var/www/target/connexion_db.php') as boumboum;
A ce niveau, les injections SQL deviennent très puissantes. Mais il faut fortement relativiser cette menace. En effet, comme dit plus haut, le droit FILES est nécessaire. De plus, il faut que MySQL ait les droits en lecture et/ou en écriture sur les fichiers manipulés, ce qui laisse tout de même une marge de manoeuvre non négligeable. Ceci dit, les fichiers de données du DBMS (tables, etc.) sont toujours atteignables.

Interférence des charsets
   Enfin, il me paraît important de prendre conscience que lorsque l'on effectue des requêtes SQL depuis une application Web, on travaille en réalité dans un mode client/serveur. Un client et un serveur communiquent selon des protocoles clairement établis et doivent être compatibles l'un de l'autre pour en assurer le bon fonctionnement. Les incompatibilités de charset entre un serveur Web ou un interpréteur et un serveur SQL peuvent s'avérer dangeureuses, notamment quand on en vient à échapper et à nettoyer des entrées. En effet, une entrée est "nettoyée" selon un charset donné. Ainsi, si l'interpréteur nettoie des données d'un charset exotique en pensant travailler sur de l'unicode et qu'il l'envoie ensuite au serveur SQL qui pense travailler sur du GBK, les effets peuvent être inattendus.
La fonction addslashes(), très populaire en PHP et notamment utilisée lorsque l'option magic_quotes_gpc est activée est conçue pour traiter des chaînes de caractères encodées sur 8 bits. Ainsi, certains charsets spéciaux codés sur 7 ou 13 bits ne seront pas compris en tant que tels et la fonction pourra alors louper des caractères spéciaux comme des quotes qui seront bels et bien interprétés si transmis tels quels au serveur SQL. Je suis le premier à dire qu'il est bon, lors de requêtes à MySQL, de nettoyer les variables avec la fonction mysql_real_escape_string de la librairie MySQL. Ceci dit, contrairement à ce que beaucoup croient, ce n'est pas tant pour régler ces problèmes de charset mais surtout pour permettre un traitement correct des caractères spéciaux de MySQL. En effet, ce genre de problèmes est également possible avec cette fonction. Pas pour les mêmes raisons certes, mais toujours à cause d'incompatibilités de charset. L'exemple le plus connu est celui du jeu de caractères GBK (jeu de caractères chinois). Les caractères spéciaux de GBK commencent par 0xBF puis forment différents caractères selon l'agglomération de bytes qui suit. Notamment, 0xBF5C est un caractère chinois (or, de manière commune, 0x5C est un antislash en unicode et dans les charset latins occidentaux). Ainsi, en imaginant n'importe quelle requête avec une quote, si l'on insère des valeurs du type blabla\xbf' UNION SELECT ... et qu'elles sont ensuite nettoyées par une fonction déchappement, on obtiendra donc blabla\xbf\' UNION SELECT .... Si MySQL s'attend à recevoir du GBK, il traitera \xbf\xc5 comme un caractère chinois (car \xbf\xc5\x27 n'est pas un caractère valide en GBK, 0x27 étant la simple quote). Ainsi, la requête devient, du point de vue du serveur SQL, blabla[caractère chinois 0xbfc5]' UNION SELECT ... et il est donc possible de passer outre l'échappement des quotes.

Un petit exemple
   Afin de tester toutes ces connaissances, je vous propose un petit défi, à savoir l'analyse des sources d'un site, BrowserWar, que j'avais créé initialement pour montrer les dangers du développement naïf. Cette version est plus avancée et contient une (ou deux selon les configurations) failles de type injection SQL. Le serveur était configuré avec un charset par défaut en UTF-8 et vous n'êtes pas censés connaître les noms des tables (dans la version qui était en ligne ils avaient été modifiés) ou des informations sur les administrateurs (dans la version finale, id/login/password avaient été modifiés et il y avait deux admins). Normalement, avec tout ce qui a été mis ci-dessus, vous devriez avoir les armes en main afin de réussir l'exploitation avec succès.
Même si je n'en attends pas moins de vous, je laisse également un lien vers ma solution qui vaut ce qu'elle vaut.

HTTP Splitting >>



46 Commentaires
Afficher tous


mekdad 18/04/14 23:43
bien utilisé


Guel_gen 06/01/14 09:27
et pourtant cette faille est bien exploitée

Anonyme 12/12/13 22:34
Facile à contourner ces failles avec un simple transtypage non ?

Anonyme 05/07/13 04:14
intval($id) ,
is_numeric($id),
ctype_digit($id)

règlent le problème des urls contenant un id





Commentaires désactivés.

Apprendre la base du hacking - Liens sécurité informatique/hacking - Contact

Copyright © Bases-Hacking 2007-2014. All rights reserved.