4.8.5.2 Tester MySQL

Sommaire
Les vulnérabilités d'injections SQL SQL Injection adviennent chaque fois qu'une entrée est utilisée dans la construction d'une requête SQL sans avoir été contrôlées et nettoyées. L'utilisation de code SQL dynamique (construction de requêtes SQL par concaténation de chaînes) ouvre la voie à ces vulnérabilités. Une injection SQL permet à un attaquant d'accéder à des serveurs SQL. Cela permet l'exécution de code SQL avec les privilèges de l'utilisateur utilisé pour se connecter à la base de données.

MySQL server comporte quelques particularités qui font que certains exploits doivent être adaptés à cette application. C'est le sujet de cette section.

Comment tester
Quand une vulnérabilité d'injection SQL est découverte dans une application utilisant une base de données MySQL, on peut procéder à un certain nombre d'attaques selon la version de MySQL et les privilèges de l'utilisateur sur le SGBD.

Il y a au moins quatre versions de MySQL largement utilisées dans le monde : 3.23.x, 4.0.x, 4.1.x et 5.0.x. Chaque version possède un ensemble de fonctionnalités grandissant.


 * Depuis la Version 4.0: UNION
 * Depuis la Version 4.1: Sous-requêtes
 * Depuis la Version 5.0: Procédures stockées, fonctions stockées la vue nommée INFORMATION_SCHEMA
 * Depuis la Version 5.0.2: Triggers

Il faut remarquer qu'avant la version 4.0.x, seules les injections en aveugle et booléennes pouvaient être utilisées, puisque les fonctionnalités de sous-requêtes ou UNION n'étaient pas implémentées.

A partir de maintenant, nous considèrerons qu'une vulnérabilité d'injection SQL est présente, pouvant être déclenchée par une requête similaire à celles décrites dans la section Testing for SQL Injection.

http://www.example.com/page.php?id=2

Le problème de la simple quote
Avant de tirer avantage des fonctionnalités de MySQL, il faut prendre en considération la manière dont les chaînes sont représentées dans une instruction, puisque souvent les applications web échappent les simples quotes.

L'échappement de quotes sous MySQL se fait comme suit :  'A string with \'quotes\'' 

MySQL interprête les apostrophes échappés (\') comme des caractères et non comme des métacaractères.

Donc si une application a besoin pour son fonctionnement d'utiliser des chaînes constantes, deux cas doivent être distingués :
 * 1) Applications web échappant les simples quotes (' => \')
 * 2) Applications web n'échappant les simples quotes (' => ')

Sous MySQL, il y a une manière standard pour éviter d'utiliser les simples quotes pour déclarer les chaînes constantes.

Supposons que nous voulions connaître la valeur d'un champ nommé 'password' dans un enregistrement, avec une condition comme :
 * password like 'A%'
 * 1) Les valeurs ASCII dans une chaîne concaténée en hexa:
 * password LIKE 0x4125
 * 1) La fonction char:
 * password LIKE CHAR(65,37)

Requêtes fixes multiples :
Les bibliothèques de connexion MySQL ne supporte pas les requêtes multiples séparées par  ';' . Il n'y a donc pas moyen d'injecter des commandes SQL multiples non-homogènes dans une vulnérabilité d'injection SQL unique, comme sous Microsoft SQL Server.

Par exemple, l'injection suivante va causer une erreur :

1 ; update tablename set code='javascript code' where 1 --

Identification de MySQL
Evidemment, la première chose à savoir est si un SGBD MySQL est utilisé. Le serveur MySQL a une fonctionnalité utilisée pour laisser d'autres SGBD ignorer une clause du dialecte MySQL. Quand un commentaire ('/**/') contient un point d'exclamation ('/*! sql here*/'), il est interprêté par MySQL, alors qu'il est considéré comme une block de commentaire normal par les autres SGBD. Ce ci est expliqué ici : MySQL manual.

Exemple: 1 /*! and 1=0 */

Resultat attendu:

Si MySQL est présent, la clause dans le block de commentaire sera interprêtée.

Version
Il y a trois manières de découvrir cette information : if(version >= 4.1.10) ajouter 'and 1=0' à la requête.
 * 1) En utilisant la vafiable globale @@version
 * 2) En utilisant la fonction [VERSION]
 * 3) En utilisant l'identification avec des commentaires incluant un numéro de version /*!40110 and 1=0*/
 * Cela signifie

Ces trois manières sont équivalentes puisque les résultats sont les même.

Injection in-band :

1 AND 1=0 UNION SELECT @@version /*

Injection Inférentielle :

1 AND @@version like '4.0%'

Resultat attendu:

Une chaîne ressemblant à : 5.0.22-log

Utilisateur connecté
Il y a deux sortes d'utilisateurs nécessaires à MySQL.
 * 1) [USER]: l'utilisateur connecté au serveur MySQL.
 * 2) [CURRENT_USER]: l'utilisateur interne qui exécute la requête.

Il y a quelques différences entre les deux. La principale est qu'un utilisateur anonyme peut se connecter (si c'est autorisé) sous n'importe quel nom, mais l'utilisateur interne est un nom vide ( '' ). Un autre différence est qu'une procédure ou fonction stockée est exécutée avec les droits de son créateur par défaut. Cela peut être déterminé en utilisant CURRENT_USER.

Injection in-band :

1 AND 1=0 UNION SELECT USER

Injection inférentielle :

1 AND USER like 'root%'

Resultat attendu:

Une chaîne de ce type : user@hostname

Base de données utilisée
Il existe un fonction native DATABASE

Injetion in-band :

1 AND 1=0 UNION SELECT DATABASE

Injection inférentielle :

1 AND DATABASE like 'db%'

Resultat attendu: Une chaîne de ce type : dbname

INFORMATION_SCHEMA
Depuis MySQL 5.0, une vue nommée [INFORMATION_SCHEMA] a été créée. Elle permet de récupérer toutes les informations sur la base de données, les tables, les colonnes, ainsi que les procédures et les fonctions.

Voici un résumé des vues intéressantes :

Toutes ces informations peuvent être extraites en utilisant les techniques connues décrites dans la section sur les injections SQL.

Ecrire dans un fichier
Si l'utilisateur connecté à le privilège FILE et que les simples quotes ne sont pas échappées, la clause 'into outfile' peut être utilisée pour exporter les résultats de requêtes dans un fichier.

Select * from table into outfile '/tmp/file'

Remarque : il n'y a aucun moyen de faire cela sans les simples quotes entourant le nom du fichier. Donc s'il y a un filtrage des simples quotes comme un échappement (\') il ne sera pas possible d'utiliser la clause 'into outfile'.

Ce type d'attaque peut être utilisé comme une technique out-of-band pour récupérer des informations sur le résultat d'un requête ou pour écrire un fichier qui pourrait être exécuté sur le serveur.

Exemple:

1 limit 1 into outfile '/var/www/root/test.jsp' FIELDS ENCLOSED BY '//' LINES TERMINATED BY '\n<%code jsp ici%>';

Resultat attendu: Les résultats sont stockés dans un fichier avec les droits rw-rw-rw appartenant à l'utilisateur et au groupe MySQL.

Où /var/www/root/test.jsp va contenir: //champ valeurs// <%code jsp ici%>

Lire un fichier
Load_file est une fonction native qui peut lire un fichier quand c'est autorisé par les permissions du système de fichiers. Si un utilisateur connecté à les privilèges FILE, cela peut être utilisé pour obtenir le contenu du fichier. Le filtrage des simples quotes peut être contourné par les techniques décrites plus haut.

load_file('filename')

Resultat attendu:

Le fichier en entier peut être disponible et exporté pes les techniques standard.

Attaque par injection SQL standard
Dans une injection SQL standard, on peut obtenir l'affichage des resultats directement dans la page ou dans un message d'erreur MySQL. En utilisant les attaques par injection SQL déjà mentionnées et les fonctionnalités MySQL déjà décrites, une injection SQL directe peut aisément être obtenue à un niveau dépendant principalement de la version de MySQL à la quelle le testeur fait face.

Une bonne attaque est de récupérer les résultats en forçant une fonction/procédure ou le serveur lui-même a remonter une erreur. Une liste d'erreurs remontées par MySQL et dans certaine fonctions natives peut être consultée ici : MySQL Manual.

Injection SQL ou-of-band
Les injection out-of-band peuvent être accomplies en utilisant la clause 'into outfile' clause.

Injection SQL en aveugle
Pour les injection SQL en aveugle, MySQL fournit un ensemble de fonctions utiles.


 * Longueur d'une chaîne:
 * LENGTH(str)
 * Extraction d'une sous-chaine à partir d'une chaîne:
 * SUBSTRING(string, offset, #chars_returned)
 * Injection en aveugle basée sur le temps : BENCHMARK et SLEEP
 * BENCHMARK(#ofcycles,action_to_be_performed )
 * La fonction benchmark peut être utilisée pour procéder à des attaques basées sur le temps, quand la méthode booleénne ne donne pas de résultat.
 * Voir SLEEP (MySQL > 5.0.x) pour une alternative à benchmark.

Pour une list complète, voir le manuel de MySQL : http://dev.mysql.com/doc/refman/5.0/en/functions.html

Outils

 * Francois Larouche: Multiple DBMS SQL Injection tool - http://www.sqlpowerinjector.com/index.htm
 * ilo--, Reversing.org - sqlbftools
 * Bernardo Damele A. G.: sqlmap, automatic SQL injection tool - http://sqlmap.org/
 * Muhaimin Dzulfakar: MySqloit, MySql Injection takeover tool - http://code.google.com/p/mysqloit/
 * http://sqlsus.sourceforge.net/

Références
Whitepapers
 * Chris Anley: "Hackproofing MySQL" - http://www.databasesecurity.com/mysql/HackproofingMySQL.pdf

Etudes de cas
 * Zeelock: Blind Injection in MySQL Databases - http://archive.cert.uni-stuttgart.de/bugtraq/2005/02/msg00289.html