L’injection SQL est une des failles de sécurité les plus dangereuses pour un site web. Un simple oubli dans le code peut exposer toutes vos données.
Heureusement, il existe des méthodes fiables pour s’en protéger. Ce guide vous explique comment une attaque fonctionne et comment la bloquer pour de bon.
Qu’est-ce qu’une injection SQL (SQLi) ?
Une injection SQL, aussi appelée SQLi, est une technique d’attaque qui consiste à insérer du code SQL malveillant dans une requête via une entrée non contrôlée d’une application web. Le but de l’attaquant est de manipuler la requête SQL originale pour qu’elle exécute des commandes qu’il a lui-même choisies.
Concrètement, si un formulaire sur votre site envoie des informations à votre base de données sans les vérifier, un pirate peut utiliser ce formulaire pour envoyer des morceaux de code SQL. La base de données, pensant que ce code fait partie de la requête légitime, l’exécute. C’est comme donner les clés de votre base de données à un inconnu.
Cette faille de sécurité touche principalement les bases de données relationnelles, qui utilisent le langage SQL. Les plus connues sont :
- MySQL
- Oracle Database
- Microsoft SQL Server (MSSQL)
- PostgreSQL
Bon à savoir : Les bases de données non relationnelles, comme MongoDB ou CouchDB, ne sont pas vulnérables aux injections SQL, mais elles peuvent être la cible d’attaques similaires appelées injections NoSQL. Le principe reste le même : injecter du code via une entrée utilisateur.
Comment fonctionne une attaque par injection SQL : l’anatomie d’une faille
Pour bien comprendre le danger, le plus simple est de voir comment une attaque injection SQL se déroule en pratique. L’exemple le plus courant est celui du contournement d’une page de connexion. C’est souvent par là que les attaquants testent la vulnérabilité d’une application web.
L’exemple du contournement d’authentification
Imaginez un formulaire de connexion classique avec deux champs : « nom d’utilisateur » et « mot de passe ». Côté serveur, le code qui vérifie ces informations pourrait ressembler à ça en PHP :
// Attention : ce code est vulnérable !
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM utilisateurs WHERE nom = '$username' AND mot_de_passe = '$password'";
// ... exécution de la requête ...
Si un utilisateur légitime entre « admin » et « motdepasse123 », la requête SQL générée sera :
SELECT * FROM utilisateurs WHERE nom = 'admin' AND mot_de_passe = 'motdepasse123'
La base de données cherche alors un utilisateur avec ce nom et ce mot de passe. Jusqu’ici, tout va bien.
La première étape de l’attaquant : le test
Un attaquant ne connaît ni le nom d’utilisateur ni le mot de passe. Il va d’abord tester si le champ est vulnérable. Pour cela, il peut simplement entrer une apostrophe (‘) dans le champ du nom d’utilisateur. La requête SQL devient alors :
SELECT * FROM utilisateurs WHERE nom = ''' AND mot_de_passe = ''
Cette requête est syntaxiquement incorrecte à cause des trois apostrophes. La base de données va renvoyer une erreur. Si l’application affiche cette erreur, même de manière indirecte, l’attaquant sait que le champ est probablement vulnérable à une injection SQL.
L’exploitation de la faille
Maintenant qu’il sait que la faille existe, l’attaquant va construire une charge utile (payload) pour tromper la logique de la requête. Une des plus célèbres est ' OR 1=1;--.
Il entre admin' OR 1=1;-- dans le champ du nom d’utilisateur et n’importe quoi dans le champ du mot de passe. La requête SQL envoyée à la base de données devient :
SELECT * FROM utilisateurs WHERE nom = 'admin' OR 1=1;--' AND mot_de_passe = 'nimportequoi'
Décortiquons ce que la base de données va interpréter :
SELECT * FROM utilisateurs WHERE nom = 'admin': La première partie de la condition.OR 1=1: L’attaquant ajoute une condition qui est toujours vraie. Comme 1 est toujours égal à 1, la condition globale de la clause `WHERE` sera vraie pour chaque ligne de la table.;: Sépare les instructions SQL (dans certains systèmes).--: C’est un commentaire en SQL. Tout ce qui suit sur la même ligne est ignoré par la base de données. La partie' AND mot_de_passe = 'nimportequoi'est donc complètement ignorée.
Au final, la requête exécutée est équivalente à SELECT * FROM utilisateurs WHERE nom = 'admin' OR 1=1. La base de données renvoie alors tous les utilisateurs de la table, et l’application connecte l’attaquant avec le premier compte de la liste, qui est souvent le compte administrateur.
Les différents types d’attaques par injection SQL
Le contournement de connexion n’est qu’un exemple. Il existe plusieurs types d’injections SQL, chacune avec une approche différente pour extraire ou manipuler des données.
- Union based (basée sur l’UNION) : C’est l’une des techniques les plus directes. L’attaquant utilise l’opérateur SQL
UNIONpour combiner le résultat de la requête légitime avec le résultat d’une autre requête qu’il a fabriquée. Cela lui permet d’extraire des informations d’autres tables de la base de données. - Error based (basée sur les erreurs) : Si l’application affiche les messages d’erreur de la base de données, un attaquant peut provoquer volontairement des erreurs pour en extraire des informations. Par exemple, il peut forcer la base de données à inclure des données sensibles (comme un mot de passe) dans le message d’erreur.
- Blind based (en aveugle) : Dans ce cas, l’attaquant ne voit ni le résultat de la requête ni les messages d’erreur. Il doit déduire les informations bit par bit. Il pose une série de questions à la base de données auxquelles la réponse est « vrai » ou « faux », et observe la réponse de l’application (par exemple, une page qui s’affiche différemment). C’est une méthode lente mais efficace.
- Time based (basée sur le temps) : C’est une sous-catégorie de l’injection en aveugle. L’attaquant injecte une commande qui demande à la base de données d’attendre un certain temps (ex: `SLEEP(5)`). S’il observe que la page met 5 secondes de plus à charger, il sait que sa condition est vraie. Il peut ainsi extraire des données caractère par caractère.
- Stacked queries (requêtes empilées) : Si la configuration du serveur le permet, un attaquant peut utiliser un point-virgule (`;`) pour terminer la première requête et en commencer une nouvelle. C’est le type d’injection le plus dangereux, car il permet d’exécuter n’importe quelle commande SQL : modifier des données (`UPDATE`), en supprimer (`DELETE`), ou même effacer des tables entières (`DROP TABLE`).
Techniques d’exploitation avancées
Une fois qu’un attaquant a confirmé une faille d’injection SQL, il peut aller beaucoup plus loin que simplement se connecter.
Énumération de la base de données
La plupart des systèmes de bases de données (comme MySQL ou MSSQL) contiennent un ensemble de tables qui décrivent la structure même de la base de données. C’est ce qu’on appelle le schéma d’information (INFORMATION_SCHEMA). En utilisant des injections SQL, un attaquant peut interroger ces tables pour découvrir :
- Le nom de toutes les bases de données (via la table
SCHEMATA). - La liste de toutes les tables d’une base de données (via la table
TABLES). - La liste de toutes les colonnes d’une table (via la table
COLUMNS).
Avec ces informations, il peut construire des requêtes précises pour extraire les données les plus sensibles, comme les tables `utilisateurs` ou `cartes_bancaires`.
Lecture et écriture de fichiers sur le serveur
Dans certaines configurations, une attaque injection SQL peut permettre de lire ou écrire des fichiers directement sur le serveur web. Pour que cela soit possible, plusieurs conditions doivent être réunies :
- L’utilisateur de la base de données que l’application web utilise doit avoir le privilège
FILE. - La configuration de la base de données (ex: la variable `secure_file_priv` sur MySQL) ne doit pas restreindre cette action.
- L’application doit avoir les permissions d’écriture sur le répertoire cible.
Si ces conditions sont remplies, un attaquant peut lire des fichiers de configuration sensibles ou, pire, écrire un fichier malveillant sur le serveur. Il peut par exemple déposer un « webshell », un petit script qui lui donne un accès complet au serveur pour exécuter des commandes à distance. À ce stade, l’attaquant a pris le contrôle total du serveur.
Comment se protéger efficacement contre les injections SQL ?
Protéger une application contre les injections SQL n’est pas une option, c’est une nécessité. Heureusement, les méthodes pour y parvenir sont bien connues et très efficaces si elles sont appliquées correctement. La défense consiste à ne jamais faire confiance aux données qui viennent de l’utilisateur.
La méthode la plus sûre et la plus recommandée par tous les experts en sécurité est d’utiliser des requêtes préparées (ou paramétrées). C’est la solution de référence pour bloquer les injections SQL.
1. Utiliser des requêtes préparées (requêtes paramétrées)
Le principe d’une requête préparée est de séparer le code SQL des données fournies par l’utilisateur. Le développeur définit d’abord un modèle de requête avec des marqueurs (`?` par exemple) à la place des données. Ensuite, il envoie les données de l’utilisateur séparément. La base de données ne considérera jamais ces données comme faisant partie du code SQL à exécuter.
En PHP avec l’extension MySQLi, cela ressemble à ça :
$stmt = $mysqli->prepare("SELECT * FROM utilisateurs WHERE nom = ? AND mot_de_passe = ?");
$stmt->bind_param("ss", $_POST['username'], $_POST['password']);
$stmt->execute();
// ... récupérer les résultats ...
Même si un attaquant injecte ' OR 1=1;--, la base de données cherchera simplement un utilisateur dont le nom est littéralement la chaîne de caractères ' OR 1=1;--, et n’en trouvera aucun. L’injection est neutralisée. C’est la fonction mysqli_stmt_bind_param() qui s’occupe de lier les paramètres de manière sécurisée.
2. Échapper les entrées utilisateur
Si pour une raison ou une autre vous ne pouvez pas utiliser de requêtes préparées, la deuxième meilleure option est d’échapper systématiquement les caractères spéciaux dans les données fournies par les utilisateurs. Échapper un caractère consiste à lui ajouter un antislash (`\`) pour que la base de données le traite comme un simple caractère et non comme un opérateur spécial (comme une apostrophe).
En PHP, la fonction correcte pour cela est mysqli_real_escape_string(). Il est crucial de ne pas utiliser d’anciennes fonctions comme addslashes(), qui ne sont pas spécifiques à la base de données et peuvent être contournées. De même, la vieille directive `magic_quotes` est obsolète et dangereuse.
3. Utiliser des procédures stockées
Une procédure stockée est un ensemble de requêtes SQL précompilées et stockées directement dans la base de données. L’application web ne fait qu’appeler ces procédures en leur passant des paramètres. Comme pour les requêtes préparées, les paramètres sont traités comme des données et non comme du code exécutable, ce qui empêche les injections SQL.
4. Appliquer le principe du moindre privilège
C’est une règle de sécurité fondamentale. Le compte utilisateur de la base de données utilisé par votre application web ne doit avoir que les permissions strictement nécessaires pour fonctionner. Par exemple :
- Il ne doit pas être administrateur de la base de données.
- Il doit uniquement pouvoir accéder aux bases de données dont il a besoin.
- Il ne devrait pas avoir le privilège
FILEs’il n’a pas besoin de lire ou écrire des fichiers. - Il ne devrait pas avoir le droit de faire des
DROP TABLE.
Ainsi, même si une injection SQL réussit, les dégâts que l’attaquant pourra causer seront limités par les faibles permissions du compte.
5. Valider et filtrer les entrées
Enfin, une bonne pratique est de toujours valider les données côté serveur. Si vous attendez un nombre, vérifiez que c’est bien un nombre (par exemple avec la fonction PHP `ctype_digit()`). Si vous attendez une adresse e-mail, vérifiez qu’elle a le bon format. En forçant le type de données, vous réduisez la surface d’attaque et empêchez de nombreuses injections.
Exemples célèbres d’attaques par injection SQL
L’injection SQL n’est pas une menace théorique. De nombreuses entreprises, y compris des géants de la tech, en ont été victimes au fil des ans, avec des conséquences souvent graves. Voici quelques exemples marquants :
- Guess.com (2002) : L’une des premières attaques SQLi médiatisées. Les données de plus de 200 000 clients, y compris leurs noms, adresses et numéros de cartes de crédit, ont été volées.
- Albert Gonzalez (2009) : Ce hacker a orchestré une série d’attaques par injection SQL massives contre des entreprises comme Heartland Payment Systems, 7-Eleven et Hannaford, volant plus de 130 millions de numéros de cartes de crédit.
- Royal Navy (2010) : Le site web de la marine britannique a été défacé et mis hors ligne par le hacker « TinKode » via une injection SQL.
- Barracuda Networks (2011) : Cette entreprise de sécurité a elle-même été victime d’une injection SQL, entraînant la fuite d’adresses e-mail et d’identifiants de ses clients.
- Sony (2011) : Le groupe de hackers LulzSec a utilisé une injection SQL pour voler les données personnelles de plus d’un million d’utilisateurs des services en ligne de Sony Pictures.
- Yahoo (2012, 2013) : L’entreprise a subi plusieurs attaques massives. L’une d’elles, en 2012, a exposé les identifiants de plus de 450 000 utilisateurs. Une autre en 2013, liée à une faille différente, a touché 1 milliard de comptes.
L’injection SQL reste une menace majeure pour la sécurité des applications web. Elle est simple à exploiter pour un attaquant mais peut avoir des conséquences désastreuses pour une entreprise : vol de données clients, usurpation d’identité, perte financière et atteinte à la réputation.
La meilleure défense reste la prévention. En adoptant des pratiques de codage sécurisées, et surtout en utilisant systématiquement des requêtes préparées, les développeurs peuvent rendre leurs applications beaucoup plus robustes face à ce type d’attaque. La sécurité n’est pas une option, c’est une responsabilité.
