SQL Injection
- MON
- 2022-2023
- temps 1
- security
- web
- connexion
- sql
- Jean-Baptiste Durand
Disclaimer
Les méthodes présentées dans ce cours peuvent permettre d'accéder de manière illégale à des informations.
L'utilisation de ces méthodes sans l'autorisation du propriétaire d'un site web ou un quelconque serveur est illégale et est punit par la loi.
Ce cours a pour objectif de sensibiliser les ingénieurs dans le domaine de la sécurité et leur permettre de protéger les serveurs contre ce type d'attaque.
Table des matières
- Disclaimer
- Table des matières
- Pourquoi cette attaque ?
- BurpSuite
- SQL injection
- Défense
Pourquoi cette attaque ?
Tout site web, sauf sites statiques, héberge une base de données SQL. Donc tous les sites web sont des potentielles victimes d'attaques par injection de SQL.
D'après les chiffres d'Akamai (lien de l'article), sur une étude menée entre Janvier 2020 et Juin 2021 :
- les attaques de type injection de SQL sont les plus présentes
- durant la durée d'étude (18 mois) il y a eu 6.2 millards de tentatives d'attaques enregistrées par injection de SQL, ce qui représente 55.88% des attaques sur le web.
Graphique provenant du même article
BurpSuite
BurpSuite est un logiciel permettant de capturer des requêtes effectuées sur un navigateur internet, de les modifier, de les renvoyer régulièrement. C'est un outil très utile, voir nécessaire pour faire de l'analyse d'API ou pour faire des attaques par injection.
Voici tous les détails pour télécharger BurpSuite :
Le site web de BurpSuite propose un service appelé WebSecurityAcademy qui permet de tester différentes attaques, l'entièreté des exemples de ce cours provient de ce site :
WebSecurityAcademy - SQL injection
SQL injection
Principe de base
Beaucoup de fonctionnalités d'un serveur nécessitent appels à une base de données SQL, avec pour paramètres des éléments envoyés par l'utilisateur.
Par exemple lors d'une connexion, un utilisateur rentre son nom d'utilisateur et son mot de passe. Puis le serveur authentifie l'utilisateur en vérifiant que les données saisies sont bien présentes dans la base de donnée des utilisateurs :
Requete envoyé par l'utilisateur pour s'authentifier
fetch("https://nomDuServeur/login",
{
method: "POST", //permet d'envoyer des données au serveur
body: JSON.stringify(
{
username : "mon_username", //permet d'envoyer les informations au serveur
password : "mon_password"
}
)
})
Interprétation par le serveur
SELECT * FROM users WHERE username = ' + requete.username + ' AND password = ' + requete.password + '
ce qui donne ici :
SELECT * FROM users WHERE username = 'mon_username' AND password = 'mon_password'
Attaquer cette mécanique
L'objectif est de ne pas avoir à passer l'étape de vérification du mot de passe.
Pour ça, on va utiliser une mécanique assez simple en programmation : les commentaires.
Pour une base de donnée Oracle, Microsoft ou PostgreSQL, les commentaires sont fait par -- Pour une base de donnée qui utilise MySQL, les commentaires sont fait par # qui vaut %23 en hexadécimal
Donc si on termine le username envoyé dans la requête par un comentaire, il n'y aura pas de vérificaton du password.
fetch("https://nomDuServeur/login",
{
method: "POST",
body: JSON.stringify(
{
username : "administrateur%27--",
password : "n'importeQuoi"
}
)
})
Le caractère %27 correspond au symbole ' de fermeture de chaine de caractère, il permet de dire que le nom d'utilisateur est fini. Pourquoi on utilise ça plutôt que ' ? Le serveur va rajouter un caractère d'échappement devant les caractères qu'il utilise notamment ', mais pas devant sa forme hexadécimale %27
Interprétation de la requête par le serveur
SELECT * FROM users WHERE username = 'administarteur'--' AND password = 'n\'importeQuoi'
Et là, vous êtes connectés en tant qu'administrateur du site.
UNION Attack
C'est sympa de pouvoir se connecter à la place de l'administrateur, mais si l'administrateur ne s'appelle pas administrateur, comment est ce qu'on peut faire pour quand même se connecter ?
Jusque là, on était limité par le fait de devoir faire des recherches dans la table par défaut de la requête : il est impossible de modifier la requête avant un WHERE qui se situe forcément après le FROM. On veut pouvoir effectuer nos propre requêtes dans d'autres table.
Pour cela on va utiliser le mot clé SQL UNION qui va réaliser la concaténation de 2 recherches dans la base de donnée : celle par défaut et celle injectée.
Spécificités d'une UNION
Il est pas possible de simplement mettre ce qu'on veut dans la 2e recherche de l'UNION, il y a certaines contraintes :
-
Il doit avoir autant de valeurs de retour des 2 recherches : ex : si la recherche par défaut envoie 2 informations (un nom et prénom d'utilisateur par exemple), la 2e recherche devra envoyer exactement 2 informations
-
Les valeurs de retour doivent être de même type : ex : le nom et prénom sont 2 chaines de caractères, la 2e recherche devra envoyer exactement 2 chaines de caractères
Trouver le nombre de colonnes
Premièrement, il faut trouver le nombre de valeur de retour de la première recherche. Pour cela il y a 2 possibilités :
- Ordonner la première recherche selon la nième colonne, grace au mot clé ORDER BY n, si il y a une errreur c'est que la colonne n n'existe pas.
- Faire une 2e recherche (avec une UNION) en envoyant simplement des NULL, l'objectif étant de trouver combien on peut mettre de NULL sans avoir d'erreur :
UNION SELECT NULL,NULL
Remarque 1 : il n'est pas possible pour une base de donnée Oracle de faire des requêtes sans le mot clé FROM, pour cela, il existe une table faite pour ça : dual
UNION SELECT NULL,NULL FROM dual
Remarque 2 : à partir de maintenant, on est obligé d'avoir des espaces dans nos requêtes (entre ORDER et BY ou entre UNION et SELECT), or il est pas possible de faire une requète HTTP avec des espaces dans les paramètres. Pour cela, il faut remplacer tous les espaces par le caractère +
Trouver le type de données envoyées
Maintenant qu'on a le nombre de colonnes, il faut tester un par un les paramètres pour savoir quel est le type du paramètre :
On envoie UNION 'string',NULL,NULL (si 3 colonnes), puis UNION 0,NULL,NULL, jusqu'à trouver le type de la première colonne et ainsi de suite.
La remarque faite sur Oracle dans la précédente partie est toujours applicable ici.
1 seule colonne
Si un il y a uniquement un champ string disponible, et qu'on veut afficher plusieurs sorties en même temps (par exemple le username et le password d'un utilisateur), on peut utiliser la fonction CONCAT de SQL:
SELECT CONCAT(username,'/',password) from users
Versions
Il peut être interressant de connaitre la version de la base de donnée pour effectuer des attaques ciblées sur la version. Les requêtes dépendent de la base de donnée :
- Oracle : SELECT banner FROM v$version
- Microsoft : SELECT @@version
- PostgreSQL : SELECT version()
- MySQL : SELECT @@version
Si besoin @ = %40 et $ = %24
Avoir le nom des tables
Pour effectuer nos recherche, il faut avoir le nom des tables et de colonnes, pour cela on peut effectuer, dans un requete UNION, les recherches suivantes :
Non Oracle
SELECT TABLE_NAME FROM information_schema.tables
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TABLE-NAME-HERE'
Oracle
SELECT TABLE_NAME FROM all_tables
SELECT COLUMN_NAME FROM all_tab_columns WHERE TABLE_NAME = 'TABLE-NAME-HERE'
Ne pas oublier de rajouter des colonnes NULL au cas où il faille plusieurs colonnes avec l'UNION
CheatSheet
Les informations plus spécifiques liés aux serveur SQL sont dans ce document : lien
Défense
Il est possible de paramétrer une requête SQL, c'est-à-dire transformer une requête en une fonction prenant en paramètre les valeurs de la requête.
Au lieu d'executer :
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
On crée un statement sans les valeurs, puis on ajoute les paramètres :
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
Voici les 3 liens où on peut retrouver des exemples de code en fonction des languages utilisés :
Attention aux ORM
Un ORM est un outils permettant de faciliter la communication entre le serveur et la base de donnée. Un ORM n'est pas forcément une protexion contre les injections de SQL.
Pour savoir si votre serveur est protégé contre ce type d'attaques, n'oubliez pas de tester par vous même la sécurité de votre site.