Sécurité : Injection SQL
L'injection SQL consiste à falsifier des requêtes SQL afin d'opérer sur une base de données.
L’injection SQL, au même titre que la faille XSS et l’attaque CSRF, est considérée comme l’une des failles de sécurité les plus courantes. Il est, pour cela, important de comprendre en quoi elle consiste et être en mesure de s’en prémunir.
Comprendre l’injection SQL
Définition
L’injection SQL est une attaque qui consiste à falsifier des requêtes SQL afin de pirater une base de données.
Mais comment l’attaquant peut-il bien s’y prendre ? 🤔
Pour opérer sur la base de données cible, l’attaquant va saisir du code SQL malicieux dans des entrées utilisateurs (formulaires) de sorte à modifier la requête SQL qui sera exécutée en base de données.
Fonctionnement
Concrètement, une injection SQL s’écrit en terminant de manière prématurée l’exécution d’une requête SQL sur le serveur pour y écrire la sienne.
Cette nouvelle requête étant non prévue par le système, sa sécurité en devient compromise.
Cela se fait avec l’usage des caractères de commentaire :
- Point-virgule
;
(fin de requête) - Double-tiret
--
ou dièse#
(commentaire SQL)
Il existe de nombreux sous-types d’injections SQL, nous n’aborderons ici que certains aspects, l’objectif étant de comprendre la mécanique.
Comprendre par l’exemple
Pour bien comprendre l’injection SQL, prenons un exemple concret.
Admettons qu’un formulaire permette de s’authentifier sur un site web. Une base de données contiendrait la table users
suivante :
Table users
:
id | username | password |
---|---|---|
1 | admin | admin |
2 | toto | azerty |
Le formulaire de connexion ressemblerait à ceci :
<h1>Connexion</h1>
<form method="POST">
<div class="form-item">
<label for="username">Login</label>
<input type="text" name="username" id="username">
</div>
<div class="form-item">
<label for="password">Mot de passe</label>
<input type="password" name="password" id="password">
</div>
<input type="submit" value="Connexion">
</form>
Ne pas préciser d’attribut action
dans la balise <form>
indique que les données seront par défaut postées à la même URL. On imagine donc que sur la page la logique de connexion est incluse.
- Le serveur vérifie que les champs
username
etpassword
sont saisis et valides. - Une requête SQL sera alors exécutée sur la base de données afin de savoir si un utilisateur avec ce username existe bien dans la table
users
.
Cette requête ressemblerait à quelque chose comme :
SELECT * FROM users WHERE username = '{username_saisi}' AND password = '{password_saisi}';
{username_saisi}
et {password_saisi}
seraient bien entendu à remplacer par les variables contenant les champs saisis, concaténées dans le langage de programmation utilisé.
Injection SQL par commentaire
L’usage de commentaire nous permettrait de contourner la mécanique d’authentification de plusieurs manières.
Cela consiste à rendre la requête SQL valide et vraie, puis enfin, d’ignorer le code SQL restant.
Cas n°1 : on ne connaît que le username
Imaginons que nous connaissons un identifiant (ici « admin ») mais pas le mot de passe associé. Nous allons donc taper dans les champs :
Champ | Valeur |
---|---|
username | admin' -- (espace final important) ou admin' # |
password | Peu importe, par exemple azerty |
La requête de connexion exécutée par le script sera donc :
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'azerty'
- L’apostrophe
'
vient fermer la chaîne correspondante au login. Ce qui est écrit après l’apostrophe est donc considéré comme du SQL, plus comme le login à comparer. - Le double tiret
--
ou le hashtag#
représentent le début d’un commentaire ; tout ce qui suit (ici, la vérification du mot de passe) n’est donc pas interprété.
Cas n°2 : on ne connaît ni username ni password
Imaginons maintenant que nous ne connaissons ni identifiant ni mot de passe. Nous allons néanmoins pouvoir remplir les champs de la sorte :
Champ | Valeur |
---|---|
username | Peu importe, par exemple toto |
password | ' or 1='1 |
La requête de connexion exécutée par le script sera donc :
SELECT * FROM users WHERE username = 'toto' AND password = '' or 1='1'
- L’apostrophe
'
vient fermer la chaîne correspondante au mot de passe. - L’instruction
or 1='1
étant tout le temps vraie, tous les enregistrements de la table vont être renvoyés. On place un guillemet avant le1
, sinon le guillemet de la requête initiale (fermant le mot de passe) serait seul et il y aurait une erreur d’exécution.
Injection SQL par fin de requête
Le point-virgule ;
permet de terminer une instruction SQL.
Il s’avère alors redoutable pour nous laisser la liberté d’exécuter notre propre requête SQL à la place de celle qui est normalement exécutée.
Saisir le mot de passe '; truncate table users; --
induirait le vidage complet de la table users en exécutant la requête SQL suivante :
SELECT * FROM users WHERE username = 'toto' AND password = ''; truncate table users; -- '
Risques de l’injection SQL
Les risques engendrés par une injection SQL sont très graves pour la simple et bonne raison que ce type de faille s’exécute directement sur le serveur de base de données et compromet le comportement des requêtes initiales.
Il est possible d’y écrire tout type de requête SQL comme des requêtes CRUD classiques (mais sur les BDD des autres !) ou de modifier le comportement de requêtes existantes :
- Vol des données
- Insertion de données
- Modification de données
- Suppression de données
- Compromission d’une mécanique (bypasser une connexion…)
Se protéger de l’injection SQL
Se protéger d’une injection SQL consiste à échapper les caractères spéciaux potentiellement néfastes ('
, --
, #
…).
Aujourd’hui, cela se fait très simplement en écrivant toutes nos requêtes dynamiques (contenant des paramètres - des entrées utilisateur) sous forme de requêtes préparées.
Pour cela, chaque langage et/ou bibliothèque propose une implémentation propre de ces requêtes préparées (l’extension PDO
de PHP, la librairie mysql
pour JS, etc.).
En conclusion, l’injection SQL représente une menace sérieuse pour la sécurité des applications web et de leurs utilisateurs puisqu’elle opère directement sur le serveur de base de données. Il est essentiel de comprendre son fonctionnement et de mettre en place des mesures de protection adéquates pour minimiser les risques. L’utilisation de requêtes préparées est considérée comme la meilleure pratique pour contrer ce type d’attaque.