Contexte
L'injection SQL (SQLi) désigne une faille de sécurité sur les applications web qui permet à un attaquant de modifier les requêtes envoyées à la base de données. Cette technique permet à l'assaillant de récupérer des données auxquelles il n'a pas accès normalement. En général, ces données appartiennent à d'autres utilisateurs ou sont des informations auxquelles l'application a accès. En outre, l'attaquant peut aussi modifier ou supprimer ces données, provoquant ainsi des changements permanents dans le contenu ou le comportement de l'application.
En réussissant une attaque par injection SQL, l'attaquant peut accéder à des données sensibles telles que des mots de passe, des informations de carte de crédit ou des données personnelles d'utilisateurs. De nombreuses violations de données signalées dans les médias ces dernières années sont le résultat d'attaques par injection SQL, causant des atteintes à la réputation et des amendes réglementaires. Dans certains cas, un attaquant peut même obtenir une porte dérobée persistante dans les systèmes d'une organisation, entraînant une compromission à long terme qui peut passer inaperçue pendant une période prolongée.
Il existe une grande variété de vulnérabilités, d'attaques et de techniques d'injection SQL, qui surviennent dans différentes situations. Voici quelques exemples courants d'injection SQL :
- Récupération des données masquées : en modifiant une requête SQL, l'attaquant peut récupérer des résultats supplémentaires.
- Subversion de la logique de l'application : en modifiant une requête, l'attaquant peut interférer avec la logique de l'application.
- Attaques UNION : l'attaquant peut récupérer des données à partir de différentes tables de base de données.
- Examen de la base de données : l'attaquant peut extraire des informations sur la version et la structure de la base de données.
- Injection SQL aveugle : les résultats d'une requête que l'attaquant contrôle ne sont pas renvoyés dans les réponses de l'application.
Récupération des données masquées
Prenons l'exemple d'une application d'achat qui affiche une liste de produits classés par catégorie. Si l'utilisateur clique sur la catégorie "Cadeaux", son navigateur va demander l'URL correspondante :
https://website.com/products?category=Sport
Ceci déclenche une requête SQL au niveau de l'application pour récupérer les informations sur les produits correspondants dans la base de données :
SELECT * FROM products WHERE category = 'Sport' AND released = 1
L'application utilise la restriction "released = 1" pour cacher les produits qui ne sont pas encore commercialisés. Les produits non commercialisés ont probablement la valeur "released = 0".
Cependant, l'application ne dispose d'aucune défense contre les attaques par injection SQL, ce qui permet à un attaquant de lancer une attaque telle que :
https://website.com/products?category=Sport'--
L'élément clé ici est que la séquence "--" (double tiret) est utilisée en SQL pour indiquer un commentaire, ce qui signifie que tout ce qui suit est interprété comme un commentaire et ignoré. Ainsi, en incluant "--" dans la requête, le reste de la requête est ignoré, y compris la condition "AND released = 1". Cela a pour effet de supprimer cette condition de la requête, de sorte que tous les produits, y compris les produits non commercialisés, sont affichés.
Subversion de la logique d'application
Prenons l'exemple d'une application qui autorise les utilisateurs à se connecter à l'aide de leur nom d'utilisateur et de leur mot de passe. Pour vérifier les informations d'identification, l'application exécute la requête SQL suivante :
SELECT * FROM users WHERE username = 'scadra' AND password = 'P@ssw0rd'
Si la requête renvoie des informations sur un utilisateur, la connexion est considérée comme réussie. Dans le cas contraire, elle est refusée.
Cependant, un attaquant peut contourner cette vérification de mot de passe en utilisant la séquence de commentaires SQL "--" pour supprimer la condition de vérification de mot de passe dans la clause WHERE de la requête. Ainsi, en soumettant le nom d'utilisateur "administrator'--" et un mot de passe vide, la requête suivante est exécutée :
SELECT * FROM users WHERE username = 'admin'--' AND password = ''
Cette requête renvoie les informations de l'utilisateur ayant pour nom d'utilisateur "administrator", permettant ainsi à l'attaquant de se connecter en tant que cet utilisateur avec succès, sans avoir besoin de fournir un mot de passe valide.
Extraction de données depuis d'autres tables de base de données.
Dans les cas où les résultats d'une requête SQL sont retournés dans les réponses de l'application, un attaquant peut exploiter une vulnérabilité d'injection SQL pour extraire des données à partir d'autres tables de la base de données. Cette technique utilise le mot-clé UNION, qui permet d'exécuter une requête SELECT supplémentaire et d'ajouter les résultats à la requête d'origine.
Par exemple :
SELECT name, description FROM products WHERE category = 'Sport'
alors un attaquant peut soumettre l'entrée :
' UNION SELECT username, password FROM users--
Avec cette technique d'injection SQL utilisant UNION, l'application peut renvoyer tous les noms d'utilisateur et les mots de passe stockés dans une table de la base de données, ainsi que les noms et descriptions de produits provenant d'une autre table.
Examen de la base de données
Après avoir identifié une vulnérabilité d'injection SQL, il est souvent utile d'obtenir des informations sur la base de données elle-même. Ces informations peuvent ensuite être exploitées ultérieurement.
Il est possible de récupérer des détails sur la version de la base de données en utilisant une requête spécifique. La manière d'y parvenir varie en fonction du type de base de données, ce qui permet de déduire le type de base de données à partir des techniques qui fonctionnent. Par exemple, pour Oracle, on peut exécuter la requête suivante :
SELECT * FROM $version
Il est également possible de déterminer quelles tables existent dans la base de données et quelles colonnes elles contiennent. Par exemple, sur la plupart des bases de données, on peut exécuter la requête suivante pour lister les tables :
SELECT * FROM information_schema.tables
Vulnérabilités d'injection SQL aveugle
De nombreuses vulnérabilités d'injection SQL sont dites aveugles, car l'application ne renvoie pas les résultats de la requête SQL ou les erreurs de base de données dans ses réponses. Bien que les vulnérabilités aveugles soient plus difficiles à exploiter que les vulnérabilités non aveugles, elles peuvent toujours être exploitées pour accéder à des données non autorisées.
En fonction de la nature de la vulnérabilité et de la base de données concernée, les techniques suivantes peuvent être utilisées pour exploiter les vulnérabilités d'injection SQL aveugle :
- Modification de la logique de la requête pour déclencher une différence détectable dans la réponse de l'application en fonction de la véracité d'une seule condition. Cela peut impliquer l'injection d'une nouvelle condition dans une logique booléenne ou le déclenchement conditionnel d'une erreur telle qu'une division par zéro.
- Déclenchement conditionnel d'un délai dans le traitement de la requête, permettant de déduire la véracité de la condition en fonction du temps nécessaire à l'application pour répondre.
- Utilisation de techniques d'attaque hors bande (OAST) pour déclencher une interaction réseau à l'extérieur de l'application. Cette technique est très puissante et peut fonctionner dans des situations où les autres techniques ne sont pas applicables. Il est souvent possible d'extraire directement les données via le canal hors bande, par exemple en plaçant les données dans une recherche DNS pour un domaine que vous contrôlez.
Comment repérer les vulnérabilités d'injection SQL ?
Pour détecter les vulnérabilités d'injection SQL, une méthode manuelle consiste à effectuer un ensemble de tests systématiques sur chaque point d'entrée de l'application. Cela peut inclure les étapes suivantes :
- Vérification des caractères de guillemets simples (') et recherche d'erreurs ou d'autres anomalies.
- Vérification d'une syntaxe spécifique à SQL qui évalue la valeur de base (d'origine) du point d'entrée et une valeur différente, et recherche de différences systématiques dans les réponses d'application résultantes.
- Vérification de conditions booléennes telles que OR 1=1 et OR 1=2, et recherche de différences dans les réponses de l'application.
- Vérification de charges utiles conçues pour déclencher des retards lorsqu'elles sont exécutées dans une requête SQL, et recherche de différences dans le temps nécessaire pour répondre.
- Soumission de charges utiles OAST conçues pour déclencher une interaction réseau hors bande lorsqu'elles sont exécutées dans une requête SQL, et surveillance de toute interaction résultante.
Injection SQL dans différentes parties de la requête
La plupart des vulnérabilités d'injection SQL se produisent dans la clause WHERE d'une requête SELECT. Cependant, il est important de noter que les vulnérabilités d'injection SQL peuvent se produire à n'importe quel endroit de la requête, ainsi que dans différents types de requêtes. Les autres endroits les plus courants où l'injection SQL se produit sont :
- Dans les instructions UPDATE, dans les valeurs mises à jour ou la clause WHERE.
- Dans les instructions INSERT, dans les valeurs insérées.
- Dans les instructions SELECT, dans le nom de la table ou de la colonne.
- Dans les déclarations SELECT, dans la clause ORDER BY.
Injection SQL dans différents contextes
Les attaques par injection SQL peuvent être effectuées à travers n'importe quelle entrée qui peut être contrôlée et traitée comme une requête SQL par l'application. Par exemple, certaines applications web acceptent des entrées au format JSON ou XML et les utilisent pour interroger la base de données. Les attaquants peuvent ainsi trouver des moyens alternatifs pour obscurcir les attaques qui sont autrement bloquées en raison des WAF et d'autres mécanismes de défense. Les implémentations faibles de ces filtres recherchent souvent des mots-clés d'injection SQL courants dans la requête, ce qui peut être contourné en encodant ou en échappant simplement des caractères dans les mots-clés interdits. Par exemple, dans une injection SQL basée sur XML, l'attaquant peut utiliser une séquence d'échappement XML pour encoder le caractère S dans la requête SELECT, permettant ainsi d'exécuter la requête malveillante sans être détecté.
Les attaquants peuvent également utiliser d'autres formats de données, tels que CSV ou YAML, pour tenter d'exploiter les vulnérabilités d'injection SQL. Il est donc essentiel pour les développeurs de mettre en place des contrôles de sécurité pour toutes les entrées utilisateur, indépendamment de leur format, afin de réduire les risques d'injection SQL.
<stockCheck>
<productId>
1
</productId>
<storeId>
34 SELECT * FROM information_schema.tables
</storeId>
</stockCheck>
Celui-ci sera décodé côté serveur avant d'être transmis à l'interpréteur SQL.
Injection SQL de second ordre
L'injection SQL de premier ordre se produit lorsque l'application intègre l'entrée utilisateur dans une requête SQL de manière non sécurisée pendant le traitement de la requête HTTP. Cependant, dans l'injection SQL de second ordre (ou injection SQL stockée), l'application stocke l'entrée utilisateur pour une utilisation future. Cette opération est effectuée en plaçant l'entrée dans une base de données, mais il n'y a aucune vulnérabilité au moment du stockage des données. Plus tard, lorsque l'application récupère les données stockées et les intègre dans une requête SQL de manière non sécurisée lors du traitement d'une requête HTTP différente, l'injection SQL de second ordre se produit.
Les développeurs sont souvent conscients des vulnérabilités de l'injection SQL et gèrent de manière sécurisée le placement initial des données dans la base de données. Cependant, ils considèrent à tort que les données stockées sont sûres lorsqu'elles sont traitées ultérieurement, car elles ont été préalablement stockées de manière sécurisée. En réalité, les données stockées peuvent être malveillantes et mener à une injection SQL de second ordre, qui peut être utilisée par les attaquants pour accéder à des informations sensibles ou compromettre le système.
L'injection SQL de second ordre est souvent plus difficile à détecter et à prévenir que l'injection SQL de premier ordre, car l'application peut stocker des données pendant une longue période avant de les utiliser ultérieurement. De plus, les vulnérabilités d'injection SQL de second ordre peuvent être difficiles à détecter, car elles nécessitent souvent une combinaison de plusieurs entrées utilisateur pour déclencher l'exploitation. Par conséquent, il est important de mettre en place des pratiques de sécurité appropriées pour détecter et prévenir l'injection SQL de second ordre, telles que la validation stricte des données d'entrée et la mise en place de mécanismes de surveillance pour détecter les activités suspectes.
Comment prévenir
Voici une reformulation : La majorité des vulnérabilités d'injection SQL peuvent être évitées en optant pour des requêtes paramétrées (ou instructions préparées) plutôt que de concaténer des chaînes dans la requête.
Lorsque l'entrée utilisateur est directement concaténée dans la requête, comme dans l'exemple suivant, cela crée une vulnérabilité potentielle à l'injection SQL :
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
Il est possible de modifier aisément ce code pour éviter que l'input de l'utilisateur ne perturbe la structure de la requête.
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
Les requêtes paramétrées sont utiles dans les situations où des données non fiables sont incluses dans une requête, telles que la clause WHERE ou les valeurs dans une instruction INSERT ou UPDATE. Cependant, elles ne peuvent pas être utilisées pour gérer les entrées non fiables dans d'autres parties de la requête, comme les noms de table ou de colonne, ou la clause ORDER BY. Pour ces cas, d'autres approches doivent être adoptées, telles que la liste blanche des valeurs d'entrée autorisées ou l'utilisation d'une logique différente pour obtenir le comportement souhaité.
Il est important de noter que pour éviter efficacement l'injection SQL, la chaîne utilisée dans la requête doit être une constante codée en dur et ne doit pas inclure de données variables provenant de n'importe quelle source. Il est risqué de décider au cas par cas si une donnée est fiable ou non et de continuer à utiliser la concaténation de chaînes pour les données considérées comme sûres. Les erreurs peuvent facilement survenir lors de l'évaluation de l'origine des données ou de la modification d'autres codes qui contournent les hypothèses sur les données qui sont entachées.