Injection SQL
Il est temps de nous pencher sur l'injection SQL. Pendant longtemps, elle a été la reine incontestée du classement OWASP Top 10, et ce, pendant des années consécutives. Bien qu'elle soit ancienne (plus de 20 ans) et qu'elle ait légèrement reculé dans ce classement, elle demeure une vulnérabilité extrêmement populaire et dangereuse.
En tant que vulnérabilité de sécurité web, l'injection SQL (SQLi) reste l'une des techniques de piratage les plus courantes utilisées par les attaquants, car elle leur permet de manipuler une base de données et d'en extraire des informations cruciales. Plus alarmant encore, un attaquant peut s'octroyer les droits d'administrateur du serveur de base de données et commettre des actes dévastateurs, comme détruire des bases de données, manipuler des transactions, divulguer des données et rendre le système vulnérable à d'autres menaces.
Examinons rapidement comment cela se produit.
Le SQL (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 quantités massives de données générées chaque jour.
Au sein d'une application, deux contextes coexistent : celui des données et celui du 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 qui sont interprétées par erreur comme du code par l'interpréteur SQL, lui permettant ainsi de recueillir des informations précieuses depuis l'application.
Effets d'une attaque par injection SQL
Une injection SQL peut être extrêmement préjudiciable pour toute application web. Elle a été la technique privilégiée derrière de nombreuses violations de données majeures, car elle offre aux attaquants un accès non autorisé à des données critiques. Ils peuvent accéder à une multitude d'informations, allant des noms d'utilisateur et mots de passe aux détails de cartes bancaires et numéros d'identification personnels.
Après avoir obtenu l'accès à ces données, les attaquants peuvent prendre le contrôle de comptes, réinitialiser des mots de passe, effectuer des achats en ligne frauduleux ou commettre d'autres types de fraudes bien plus graves.
Mais l'aspect peut-être le plus alarmant de l'injection SQL 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îne des violations de données répétées tant que cette porte dérobée reste ouverte. C'est effrayant.
Examinons quelques exemples pour mieux comprendre comment cela se traduit concrètement.
Exemples d'injections SQL
L'injection SQL regroupe diverses techniques de vulnérabilité adaptées à différentes situations. Voici quelques-uns des exemples d'injections SQL les plus courants :
Types d'injections SQL
Voyons maintenant les trois différents types d'injections SQL.
Injection SQL in-band
Il s'agit de l'un des types d'injection SQL les plus courants, simples et efficaces. Dans ce type d'attaque, le même canal de communication est utilisé pour attaquer et pour récupérer les résultats.
Voici les deux types d'attaques par injection SQL in-band :
- Injection SQL basée sur UNION - L'attaque basée sur 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 les résultats dans une réponse HTTP GET.
- Injection SQL basée sur les erreurs - L'attaquant utilise les messages d'erreur de la base de données pour comprendre sa structure. Dans cette attaque, l'attaquant peut envoyer de fausses requêtes ou effectuer des actions pour forcer le serveur à afficher des messages d'erreur afin d'obtenir des informations sur la base de données. C'est pourquoi il est crucial que les développeurs évitent d'envoyer des erreurs ou des journaux d'événements dans l'environnement de production ; ils doivent plutôt être stockés avec un accès restreint.
Injection SQL inférentielle
Les attaques par injection SQL inférentielle, ou « aveugle », sont plus complexes et peuvent prendre plus de temps à exploiter. De plus, l'attaquant n'obtient pas directement les résultats de l'attaque, ce qui justifie le terme d'attaque « aveugle ».
L'attaquant envoie des 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 observe la réponse et le comportement de l'application pour déterminer si l'attaque a réussi.
Il existe deux types d'attaques par injection SQL inférentielle :
- Injection SQL aveugle basée sur les booléens - 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 ce résultat.
- Injection SQL aveugle basée sur le temps - Dans cette attaque, l'attaquant envoie une requête à la base de données pour la forcer à attendre quelques secondes avant de répondre, puis déduit les résultats de la requête à partir du temps de réponse de la requête HTTP.
Injection SQL hors bande (Out-of-band)
Il s'agit d'un type d'attaque par injection SQL plus rare qui dépend des fonctionnalités activées sur le serveur de base de données. Elle survient lorsque l'attaquant ne peut pas utiliser les autres types d'attaques.
Par exemple, s'il ne peut pas utiliser le même canal de communication que pour une attaque en bande, ou si la réponse HTTP n'est pas assez claire pour lui permettre de déterminer les résultats de la requête.
De plus, cette méthode est peu courante car elle repose énormément sur la capacité du serveur de base de données à effectuer des requêtes HTTP ou DNS pour transmettre les données requises à l'attaquant.
Comment se protéger contre les injections SQL
Heureusement, le point positif du fait que l'injection SQL soit une vulnérabilité ancienne et répandue est qu'il existe des moyens de l'empêcher. L'utilisation de ces techniques de prévention n'est pas seulement une bonne pratique de développement, elle renforcera considérablement la sécurité d'une organisation face aux injections SQL.
Il existe de multiples façons de sécuriser les serveurs de base de données contre ce type d'attaques, telles que la validation des entrées, l'utilisation d'un pare-feu d'application web (WAF), la sécurisation des bases de données, le recours à des équipes ou des systèmes de sécurité tiers, et l'écriture de requêtes SQL infaillibles.
Examinons un exemple de prévention des injections SQL en Python en utilisant l'une des mesures de sécurité mentionnées ci-dessus.
Exemple en Python
Dans cet exemple, l'attaquant utilisera une injection SQL aveugle basée sur les booléens pour extraire des informations sensibles du système.
Python : Vulnérable
Supposons qu'il existe une table appelée « sample_data » dans la base de données. Cette table stocke les noms d'utilisateur et les mots de passe des utilisateurs de l'application.
Permettons maintenant à l'utilisateur de rechercher une valeur dans cette table de base de données via les commandes suivantes :
import mysql.connector
db = mysql.connector.connect
#Mauvaise pratique. À éviter ! Ceci est uniquement à des fins pédagogiques.
(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) for row in cur.fetchall(): print(row)
db.close()
Injection SQL
Ici, si l'utilisateur saisit un nom dans la recherche, par exemple Alicia, le résultat ne posera aucun problème.
Cependant, si l'utilisateur saisit quelque chose comme Alicia’; DROP TABLE sample_data; , cela aura un impact significatif sur la base de données.
Python : Remédiation
L'instruction SQL doit être modifiée comme suit pour empêcher l'attaque :
cur.execute("SELECT * FROM sample_data WHERE Name = %s;", (name,))
Désormais, le système traitera la saisie de l'utilisateur comme une simple chaîne de caractères, même si celui-ci tente d'y injecter des requêtes SQL, et ne la considérera que comme la valeur du nom.
Ce changement simple permet de prévenir les activités malveillantes dans les requêtes futures et de sécuriser le système contre les attaques par injection.
Exemple en Java
Pour cet exemple, nous utiliserons également une table de base de données nommée « sample_data » qui stocke les données des utilisateurs de l'application.
Une page de connexion classique demande un nom d'utilisateur et un mot de passe, et le fichier Java, qui est une servlet (LoginServlet), les valide auprès de la base de données pour autoriser l'accès.
Java : Exemple vulnérable
En utilisant la table « sample_data » de la base de données, le système permet aux utilisateurs de se connecter en saisissant leurs identifiants.
Le fichier LoginServlet contient la requête suivante pour gérer l'opération de connexion :
//Mauvais exemple. Ne pas utiliser la concaténation de chaînes.
String query = "select * from sample_data where username='" + username + "' and password = '" + password + "'";
Connection conn = null;
Statement stmt = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
if (rs.next()) {
// Connexion réussie si une correspondance est trouvée
success = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stmt.close();
conn.close();
} catch (Exception e) {}
}
if (success) {
response.sendRedirect("home.html");
} else {
response.sendRedirect("login.html?error=1");
}
}
Voici la requête utilisée pour la connexion de l'utilisateur :
select * from sample_data where username='username' and password ='password'
Injection SQL
Le système fonctionnera parfaitement si la saisie est valide. Par exemple, reprenons le nom d'utilisateur Alicia avec le mot de passe « secret ».
Le système renverra les données de l'utilisateur correspondant à ces identifiants. Cependant, un attaquant peut manipuler la requête utilisateur via Postman ou cURL pour effectuer une 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, ce qui validera l'opération de connexion.
Java : Prévention
Pour la prévention, nous devons modifier le code LoginValidation et utiliser PreparedStatement au lieu de Statement pour l'exécution des requêtes. Ce changement évitera la concaténation du nom d'utilisateur et du mot de passe dans la requête et les traitera comme des données de paramètres afin de prévenir les injections SQL.
Voici le code modifié pour LoginValidation :
String query = "select * from sample_data where username=? and password = ?";
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
stmt = conn.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
success = true;
}
rs.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stmt.close();
conn.close();
} catch (Exception e) {
}
}
Dans ce cas, le PreparedStatement, les setters et l'API JDBC sous-jacente prendront en charge les entrées utilisateur et empêcheront l'injection SQL.

Exemples
Examinons maintenant quelques exemples supplémentaires dans divers langages pour mieux comprendre comment cela se traduit concrètement.
C# - Non sécurisé
Cet exemple n'est pas sécurisé en raison de l'utilisation de `FromRawSql`. Cette méthode ne lie pas les paramètres et ne tente pas de les échapper. Par conséquent, cette méthode doit être évitée à tout prix.
var blogs = context.Posts
.FromRawSql("SELECT * FROM Posts WHERE state = {0} AND 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ètre.
Bien que cela soit généralement sécurisé, cela présente le risque d'être très similaire à `FromRawSql`, qui n'est pas sécurisé.
var blogs = context.Posts
.FromSqlInterpolated($"SELECT * FROM Posts WHERE state = {state} AND author = {author}")
.ToList();
Java - Sécurisé : Hibernate - Named Query + Native Query
Hibernate propose deux méthodes pour construire des requêtes de manière sécurisée via ses fonctionnalités `Native Query` et `Named Query`. Toutes deux permettent de spécifier l'emplacement des paramètres.
@NamedNativeQuery(
name = "find_post_by_state_and_author",
query =
"SELECT * " +
"FROM Post " +
"WHERE state = :state" +
" AND author = :author",
resultClass = Post.class)
java
List<Post> posts = session.createNativeQuery(
"SELECT * " +
"FROM Post " +
"WHERE state = :state" +
" AND author = :author" )
.addEntity(Post.class)
.setParameter("state", state)
.setParameter("author", author)
.list();
Java - Sécurisé : jplq
En annotant un attribut `Query` sur une interface de dépôt jplq, les requêtes peuvent prendre plusieurs formes et sont paramétrées.
@Query("SELECT p FROM Post p WHERE u.state = ?1 and u.author = ?2")
Post findPostByStateAndAuthor(String state, int author);
@Query("SELECT p FROM Post p WHERE u.state = :state and u.author = :author")
User findPostByStateAndAuthor(@Param("state") String state, @Param("author") int author);
Javascript - Sécurisé : pg
Lors de l'utilisation de la bibliothèque `pg`, la méthode `query` permet la paramétrisation en fournissant les valeurs des paramètres via son second argument.
const { posts } = await db.query('SELECT * FROM Post WHERE state = $1 AND author = $2', [state, author])
Javascript - Sécurisation : Sequelize
La bibliothèque `sequelize` permet de paramétrer une requête via son second argument, qui accepte les paramètres de configuration. Cela inclut une liste de valeurs à lier à la requête, soit par nom, soit par index.
await sequelize.query(
'SELECT * FROM Post WHERE state = $state AND author = $author',
{
bind: { state: state, author: author},
type: QueryTypes.SELECT
}
);
await sequelize.query(
'SELECT * FROM Post WHERE state = $1 AND author = $2',
{
bind: [state, author],
type: QueryTypes.SELECT
}
);
Le codage sécurisé en pratique
Explorez nos directives de développement sécurisé pour comprendre et atténuer les vulnérabilités logicielles telles que l'OWASP Top 10, et plongez dans des missions de formation guidées pour vous exercer sur des simulations d'applications réelles.