hero bg no divider
Directives

Injection SQL

Il est temps de passer à l'injection SQL. Pendant longtemps, il a été le roi incontesté du Top 10 de l'OWASP, et ce, des années de suite. Bien qu'elle soit si ancienne (environ 20 ans) et bien qu'elle ait légèrement chuté par rapport à la première place de cette liste, il s'agit toujours d'une vulnérabilité incroyablement populaire et dangereuse.

En tant que faille de sécurité Web, l'injection SQL (SQLi) reste 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. Fait encore plus alarmant, un attaquant peut se faire passer pour administrateur du serveur de base de données et effectuer des actions vraiment dévastatrices, comme détruire des bases de données, manipuler des transactions, divulguer des données et les rendre vulnérables à d'autres problèmes.

Jetons un coup d'œil à la façon dont cela se passe

SQL (ou Structured Query Language) est le langage utilisé pour communiquer avec les bases de données relationnelles ; c'est le langage de requête utilisé par les développeurs, les administrateurs de bases de données et les applications pour gérer les énormes quantités de données générées chaque jour.

Au sein d'une application, deux contextes existent : 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. L'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.

Effets d'une attaque par injection SQL

Une injection SQL peut être extrêmement dangereuse pour n'importe quelle application Web et a été la technique préféré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.

Après avoir 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 pires).

Mais ce qui est peut-être le plus alarmant à 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 reste ouverte. Des trucs effrayants.

Examinons quelques exemples pour mieux comprendre à quoi cela ressemble en action.

Exemples SQLi

SQLi inclut diverses techniques de vulnérabilité qui peuvent faire face à différentes situations. Vous trouverez ci-dessous quelques-uns des exemples SQLi les plus courants :

Technique Description
Retrieving Hidden Data With this technique, attackers can modify any SQL query to gather more information from the database.
Data Examination Attackers can extract information about the version and structure of a database which helps them exploit further information. This technique may differ across databases.
Union Attacks Attackers can extract information about the version and structure of a database which helps them exploit further information. This technique may differ across databases.
Blind SQLi With Blind SQLi, attackers can implement the query on a database. The catch is that the attackers control this query, and it does not return any results in the application’s response.
Subverting Application Logic Attackers interfere with or manipulate a query to disrupt the application’s logic. To manipulate the query, attackers can combine the SQL comment sequence “--” and a WHERE clause.

Types de SQLi

Bien, regardons 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 utilise les messages d'erreur de la base de données pour comprendre sa structure. Lors de cette attaque, l'attaquant peut envoyer de fausses demandes ou effectuer des actions pour que le serveur affiche des messages d'erreur afin qu'il puisse recevoir les informations de la base de données. C'est pourquoi il est important que les développeurs évitent d'envoyer des erreurs ou de consigner des messages dans l'environnement réel ; ils doivent plutôt être stockés avec un accès restreint.

SQL inférentiel

Les attaques SQLi inférentielles ou aveugles sont plus compliquées et leur exploitation peut prendre plus de temps. En plus de cela, l'attaquant n'obtient pas les résultats de l'attaque immédiatement, ce qui en fait une attaque aveugle.

L'attaquant envoie les charges utiles via des requêtes HTTP au serveur de base de données pour restructurer la base de données de l'utilisateur, puis il observe la réponse et le comportement de l'application pour voir si l'attaque a réussi ou non.

Il existe deux types d'attaques SQLi inférentielles :

  • Blind SQLi basé sur booléen - Dans cette attaque, une requête est envoyée à la base de données pour 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.
  • Blind SQLi 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'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 défendre contre SQLi

Heureusement, l'avantage de l'injection SQL étant si ancienne et si courante, c'est qu'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 habitude de codage, elle renforcera également la sécurité d'une organisation par rapport à SQLi.

Il existe plusieurs moyens de protéger les serveurs de base de données contre ce type d'attaques, comme 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.

Voyons 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 pour récupérer des informations importantes du système.

Python : vulnérable

Supposons qu'il existe une table appelée « sample_data » dans la base de données. Ce tableau contient les noms d'utilisateur et les mots de passe des utilisateurs de l'application.

Autorisez maintenant l'utilisateur à trouver 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. Evite ça ! 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' ; » % name) pour la ligne dans cur.fetchall () : print (row)
db.fermer ()

Injection SQL

Ici, si l'utilisateur saisit un nom dans la recherche, par exemple Alicia, il n'y aura aucun problème avec la sortie.

Cependant, si l'utilisateur saisit quelque chose comme « Alicia » ; DROP TABLE sample_data ; cela affectera la base de données de manière significative.

Python : Remédiation

L'instruction SQL doit être modifiée comme suit pour empêcher l'attaque de se produire :

cur.execute (« SÉLECTIONNEZ * DEPUIS SAMPLE_DATA OÙ Nom = %s ; », (name,))

Désormais, le système traitera l'entrée utilisateur comme une chaîne, même si l'utilisateur essaie d'y injecter des requêtes SQL, et traitera l'entrée 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 et le fichier Java, qui est un servlet (LoginServlet), les valide par rapport à la base de données pour permettre l'opération de connexion.

Java : exemple vulnérable

À 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 informations d'identification 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 :

sélectionnez * 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 parfaitement 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. Cependant, un attaquant peut manipuler la demande de l'utilisateur à l'aide de Postman et de cURL pour l'injection SQL.

Par exemple, le pirate 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 l'opération de connexion soit réussie.

Java : prévention

Pour des raisons de prévention, nous devons modifier le code LoginValidation et utiliser PreparedStatement au lieu 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.

Vous trouverez ci-dessous 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 pour mieux comprendre à quoi cela ressemble en action.

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. En tant que telle, 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 prend les valeurs interpolées et les paramétrise.

Bien que cela soit généralement sécurisé, il risque d'être 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ûre 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 second paramètre.

const {posts} = await db.query (« SÉLECTIONNEZ * DEPUIS LE MESSAGE OÙ L'ÉTAT = 1$ ET L'auteur = 2$ », [état, auteur])

Javascript - Sécurisé : Sequelize

La bibliothèque sequelize fournit un moyen de paramétrer une requête via son deuxième argument, qui prend les paramètres de la requête. Cela inclut une liste de valeurs à lier à la requête en tant que paramètre, soit par nom, soit par index.

attendez sequelize.query ()
« SÉLECTIONNEZ * DEPUIS LE MESSAGE OÙ state = $state ET author = $author',
{
bind : {état : État, auteur : auteur},
type : QueryTypes.Select
}
) ;
attendez sequelize.query ()
« SÉLECTIONNEZ * À PARTIR DU MESSAGE OÙ L'ÉTAT = 1$ ET L'auteur = 2$ »,
{
bind : [État, auteur],
type : QueryTypes.Select
}
) ;