
Injection SQL
Il est temps de passer à l'injection SQL. Pendant longtemps, elle a été la vulnérabilité dominante du Top 10 de l'OWASP, et ce, pendant plusieurs années consécutives. Bien qu'elle soit ancienne (environ 20 ans) et qu'elle ait légèrement reculé par rapport à la première place de cette liste, il s'agit toujours d'une vulnérabilité extrêmement répandue et dangereuse.
En tant que faille de sécurité Web, l'injection SQL (SQLi) demeure l'une des techniques de piratage les plus couramment utilisées par les attaquants, car elle leur permet de manipuler une base de données et d'en extraire des informations cruciales. Il est particulièrement préoccupant de noter qu'un attaquant peut se faire passer pour l'administrateur du serveur de base de données et effectuer des actions extrêmement préjudiciables, telles que détruire des bases de données, manipuler des transactions, divulguer des données et les rendre vulnérables à d'autres problèmes.
Examinons comment cela se déroule
SQL (ou Structured Query Language) est le langage utilisé pour interagir avec les bases de données relationnelles ; il s'agit du langage de requête employé par les développeurs, les administrateurs de bases de données et les applications pour gérer les volumes considérables de données générés quotidiennement.
Dans une application, deux contextes coexistent : l'un pour les données, l'autre pour le code. Le contexte du code indique aux ordinateurs ce qu'ils doivent exécuter et le sépare des données à traiter. Une injection SQL se produit lorsqu'un attaquant saisit des données traitées par erreur comme du code par l'interpréteur SQL, ce qui lui permet de recueillir des informations précieuses à partir de l'application.
Conséquences d'une attaque par injection SQL
Une injection SQL peut être extrêmement préoccupante pour n'importe quelle application Web et a été la technique privilégiée à l'origine de nombreuses violations très médiatisées, car elle permet aux attaquants d'accéder sans autorisation à des données critiques. Ils peuvent consulter de nombreuses informations, telles que les noms d'utilisateur et les mots de passe, les détails des cartes de crédit et les numéros d'identification personnels.
Une fois qu'ils ont accédé à ces données, les attaquants peuvent prendre le contrôle de comptes, réinitialiser des mots de passe, effectuer des achats en ligne prolongés ou commettre d'autres types de fraudes (bien plus graves).
Cependant, ce qui est peut-être le plus préoccupant à propos de SQLi, c'est qu'un attaquant peut, s'il n'est pas détecté, maintenir une porte dérobée dans le système pendant de longues périodes. Comme vous pouvez l'imaginer, cela entraînerait des violations de données répétées aussi longtemps que la porte dérobée resterait ouverte. Des éléments inquiétants.
Examinons quelques exemples afin de mieux comprendre comment cela se présente en pratique.
Exemples SQLi
SQLi comprend diverses techniques de vulnérabilité pouvant être utilisées dans différentes situations. Vous trouverez ci-dessous quelques-uns des exemples SQLi les plus courants :
Types de SQLi
Bien, examinons maintenant les trois types de SQLi différents.
SQLi intrabande
Il s'agit de l'un des types d'injection SQL les plus courants, les plus simples et les plus efficaces. Dans ce type d'attaque, le même canal de communication est utilisé pour attaquer et récupérer le ou les résultats.
Les deux types d'attaques SQLi intrabande sont les suivants :
- SQLi basé sur Union - L'attaque basée sur l'union utilise l'opérateur union pour combiner deux requêtes SQL ou plus, telles que des instructions SELECT, afin d'obtenir les informations souhaitées et d'obtenir une réponse HTTP GET.
- SQLi basé sur les erreurs - L'attaquant exploite les messages d'erreur de la base de données pour en comprendre la structure. Lors de cette attaque, l'attaquant peut envoyer de fausses requêtes ou effectuer des actions afin que le serveur affiche des messages d'erreur, lui permettant ainsi d'obtenir des informations de la base de données. Il est donc essentiel que les développeurs évitent d'envoyer des erreurs ou d'enregistrer des messages dans l'environnement réel ; ceux-ci doivent plutôt être stockés avec un accès restreint.
SQL inférentiel
Les attaques SQLi inférentielles ou aveugles sont plus complexes et leur exploitation peut nécessiter davantage de temps. De plus, l'attaquant n'obtient pas immédiatement les résultats de l'attaque, ce qui en fait une attaque aveugle.
L'attaquant transmet les charges utiles via des requêtes HTTP au serveur de base de données afin de restructurer la base de données de l'utilisateur, puis il observe la réponse et le comportement de l'application pour déterminer si l'attaque a été couronnée de succès ou non.
Il existe deux types d'attaques SQLi inférentielles :
- SQLi aveugle basé sur booléen - Dans cette attaque, une requête est envoyée à la base de données afin d'obtenir un résultat booléen (vrai ou faux), et l'attaquant observe la réponse HTTP pour prédire le résultat booléen.
- SQLi aveugle basé sur le temps - Dans cette attaque, l'attaquant envoie une requête à la base de données pour la faire attendre quelques secondes avant d'envoyer la réponse, et l'attaquant évalue les résultats de la requête en fonction du temps de réponse de la requête HTTP.
SQLi hors bande
Il s'agit d'un type d'attaque SQLi plus rare qui dépend des fonctionnalités activées du serveur de base de données. Cela se produit dans les cas où l'attaquant ne peut pas vraiment utiliser les autres types d'attaques. Par exemple, s'il ne peut pas utiliser le même canal de communication pour l'attaque intrabande, ou si la réponse HTTP n'est pas suffisamment claire pour qu'il puisse calculer les résultats de la requête. Par exemple, s'ils ne peuvent pas utiliser le même canal de communication pour l'attaque intrabande, ou si la réponse HTTP n'est pas suffisamment claire pour qu'ils puissent calculer les résultats de la requête.
De plus, ce n'est pas si courant en raison de sa dépendance massive à la capacité du serveur de base de données à effectuer des requêtes HTTP ou DNS pour envoyer les données requises à l'attaquant.
Comment se protéger contre les attaques SQLi
Heureusement, l'avantage de l'injection SQL étant si ancienne et si courante, il existe des moyens de l'empêcher de se produire. L'utilisation de ce type de techniques de prévention n'est pas seulement une bonne pratique de codage, elle renforcera également la sécurité d'une organisation par rapport à SQLi.
Il existe plusieurs méthodes pour protéger les serveurs de bases de données contre ce type d'attaques, telles que la validation des entrées, l'utilisation d'un pare-feu d'applications Web (WAF), la sécurisation des bases de données, le recours à des équipes ou à des systèmes de sécurité tiers et la rédaction de requêtes SQL infaillibles.
Veuillez considérer un exemple de prévention des injections SQL en Python en utilisant l'une des mesures de sécurité mentionnées ci-dessus.
Exemple Python
Dans cet exemple, l'attaquant utilisera une injection SQL aveugle de type booléen afin d'obtenir des informations importantes du système.
Python : présentant des vulnérabilités
Supposons qu'il existe une table nommée « sample_data » dans la base de données. Cette table contient les noms d'utilisateur et les mots de passe des utilisateurs de l'application.
Veuillez autoriser l'utilisateur à rechercher une valeur dans cette table de base de données à l'aide des commandes suivantes :
importer mysql.connector
base de données = mysql.connector.connect
Pratique #Bad. Veuillez éviter cela ! C'est juste pour apprendre.
(host="localhost », user="newuser », passwd="pass », db="sample »)
cur = db.cursor ()
name = raw_input ('Entrez le nom : ')
cur.execute (« SELECT * FROM sample_data WHERE Name = '%s' ; » % nom) pour la ligne dans cur.fetchall () : print (row)
db.fermer ()
Injection SQL
Dans ce cas, si l'utilisateur saisit un nom dans la recherche, par exemple Alicia, il n'y aura aucun problème avec le résultat.
Cependant, si l'utilisateur saisit quelque chose comme « Alicia » ; DROP TABLE sample_data ; cela affectera considérablement la base de données.
Python : Remédiation
L'instruction SQL doit être modifiée comme suit afin d'empêcher l'attaque de se produire :
cur.execute (« Sélectionnez * depuis SAMPLE_DATA où Nom = %s ; », (nom,))
À partir de maintenant, le système traitera la saisie de l'utilisateur comme une chaîne, même si l'utilisateur tente d'y insérer des requêtes SQL, et traitera la saisie de l'utilisateur comme la valeur du nom uniquement.
Cette simple modification peut empêcher les activités malveillantes lors des prochaines requêtes et protéger le système contre les attaques par saisie par les utilisateurs.
Exemple Java
Pour cet exemple, nous utiliserons également une table de base de données nommée « sample_data » qui stocke les données utilisateur de l'application.
Une page de connexion de base utilise un nom d'utilisateur et un mot de passe. Le fichier Java, qui est un servlet (LoginServlet), les valide par rapport à la base de données afin de permettre l'opération de connexion.
Java : exemple de vulnérabilité
À l'aide de la table « sample_data » de la base de données, le système permet aux utilisateurs d'effectuer des opérations de connexion en utilisant leurs identifiants comme entrée.
Le fichier LoginServlet contient une requête adaptée à l'opération de connexion, à savoir :
//Mauvais exemple. N'utilisez pas la concaténation de chaînes.
String query = « select * from sample_data where username=' » + username + « 'and password =' » + password + « '» ;
Connexion conn = null ;
Déclaration stmt = null ;
essayez {
conn = DriverManager.getConnection (« jdbc:mysql : //127.0.0. 1:3306 /user », « root ») ;
stmt = conn.createStatement () ;
ResultSet rs = STMT.ExecuteQuery (requête) ;
si (rs.next ()) {
//Connexion réussie si une correspondance est trouvée
succès = vrai ;
}
} catch (Exception e) {
e. printStackTrace () ;
} enfin {
essayez {
stmt.fermer () ;
conn.close () ;
} catch (Exception e) {}
}
si (succès) {
response.sendRedirect (» home.html «) ;
} autre {
Response.sendRedirect (» login.html ? erreur = 1") ;
}
}
Voici la requête pour la connexion de l'utilisateur :
Veuillez sélectionner * dans sample_data où nom d'utilisateur = nom d'utilisateur = nom d'utilisateur et mot de passe = mot de passe
Injection SQL
Le système fonctionnera correctement si l'entrée est valide. Par exemple, nous dirons que le nom d'utilisateur est à nouveau Alicia et que le mot de passe est secret.
Le système renverra les données de l'utilisateur avec ces informations d'identification. Toutefois, un attaquant pourrait manipuler la requête de l'utilisateur à l'aide de Postman et de cURL pour l'injection SQL.
Par exemple, le pirate informatique peut envoyer un nom d'utilisateur fictif (Alicia) et le mot de passe « or '1'='1' ».
Dans ce cas, le nom d'utilisateur et le mot de passe ne correspondront pas, mais la condition « 1'='1 » sera toujours vraie pour que la connexion soit établie.
Java : prévention
Pour des raisons de prévention, il est nécessaire de modifier le code LoginValidation et d'utiliser PreparedStatement à la place de Statement pour l'exécution des requêtes. Cette modification empêchera la concaténation du nom d'utilisateur et du mot de passe dans la requête et les traitera comme des données de réglage afin d'éviter l'injection SQL.
Ci-dessous, vous trouverez le code modifié pour LoginValidation :
String query = « select * from sample_data where username= ? et mot de passe = ? » ;
Connexion conn = null ;
PreparedStatement stmt = null ;
essayez {
conn = DriverManager.getConnection (« jdbc:mysql : //127.0.0. 1:3306 /user », « root ») ;
stmt = Conn.PrepareStatement (requête) ;
STMT.setString (1, nom d'utilisateur) ;
STMT.setString (2, mot de passe) ;
ResultSet rs = STMT.executeQuery () ;
si (rs.next ()) {
succès = vrai ;
}
rs.fermer () ;
} catch (Exception e) {
e. printStackTrace () ;
} enfin {
essayez {
stmt.fermer () ;
conn.close () ;
} catch (Exception e) {
}
}
Dans ce cas, PreparedStatement, les setters et l'API JDBC sous-jacente se chargeront de la saisie utilisateur et empêcheront l'injection SQL.

Exemples
Nous allons maintenant examiner quelques exemples supplémentaires dans différentes langues afin de mieux comprendre comment cela se présente en pratique.
C# - Non sécurisé
Cet exemple n'est pas sécurisé en raison de son utilisation de FromRawSQL. Cette méthode ne lie pas les paramètres et ne tente pas d'y échapper. Par conséquent, cette méthode doit être évitée à tout prix.
var blogs = context.POSTS
.fromRawSQL (« SÉLECTIONNEZ * PARMI LES ARTICLES OÙ state = {0} ET author = {1} », state, author)
.toList () ;
C# - Sécurisé
Cet exemple est sécurisé grâce à FromSQLInterpolated, qui utilise les valeurs interpolées et les paramètre.
Bien que cela soit généralement sécurisé, il existe un risque que cela soit très similaire à FromRawSQL, qui n'est pas sécurisé.
var blogs = context.POSTS
.fromSQLInterpolated ($"SÉLECTIONNEZ * PARMI LES ARTICLES OÙ state = {state} ET author = {author} »)
.toList () ;
Java - Sécurisé : Hibernate - Requête nommée + Requête native
Hibernate propose deux méthodes pour construire des requêtes de manière sécurisée via sa « requête native » et sa « requête nommée ». Les deux permettent de spécifier l'emplacement des paramètres.
@NamedNativeQuery (
name = « find_post_by_state_and_author »,
requête =
« SÉLECTIONNEZ *" +
« DE LA POSTE » +
« WHERE state =:state » +
« ET auteur = : auteur »,
Classe de résultats = Post.class)
java
Liste des <Post>messages = Session.createNativeQuery (
« SÉLECTIONNEZ *" +
« DE LA POSTE » +
« WHERE state =:state » +
« ET auteur = : auteur »)
.Ajouter une entité (Post.class)
.setParameter (« état », état)
.setParameter (« auteur », auteur)
.liste () ;
Java - Sécurisé : jplq
En annotant un attribut `Query` sur une interface de référentiel jplq, ils peuvent prendre plusieurs formes et sont paramétrés.
@Query (« Sélectionnez p depuis le message p où u.state = ? 1 et u.author = ? 2 pouces)
Post findPostByStateAndAuthor (String state, int author) ;
@Query (« SÉLECTIONNEZ p DEPUIS Post p OÙ u.state =:state et u.author =:author »)
Utilisateur findPostByStateAndAuthor (@Param (« state ») String state, @Param (« author ») int author) ;
Javascript - Sécurisé : pg
Lorsque vous utilisez la bibliothèque pg, la méthode query permet le paramétrage en fournissant des valeurs de paramètres via son deuxième paramètre.
const {posts} = await db.query (« SELECT * FROM MESSAGE WHERE STATUS = 1$ AND AUTHOR = 2$ », [status, author])
Javascript - Sécurisé : Sequelize
La bibliothèque Sequelize offre la possibilité de paramétrer une requête via son deuxième argument, qui reçoit les paramètres de la requête. Cela inclut une liste de valeurs à associer à la requête en tant que paramètre, soit par nom, soit par index.