Injection SQL : comment protéger son système de base de données
Les entreprises d’une certaine taille peuvent difficilement agir sans système de base de données : ce dernier permet la gestion et l’enregistrement des informations de comptes structurées dans des systèmes de sauvegarde. De nombreuses entreprises prévoient leurs ressources avec des systèmes de planification de ressources d’entreprise qui ne peuvent fonctionner sans base de données. De même, il serait inconcevable de concevoir la plupart des offres du World Wide Web sans systèmes de bases de données. Mettre en place et prendre soin de ses fichiers électroniques exigent beaucoup de travail. Le challenge principal réside toutefois à garantir leur sécurité. Les procédures de back-up et l’utilisation de hardwares sûrs sont aussi importantes que les mesures de protection complètes contre les accès externes. Ce que l’on appelle les injections SQL représentent quant à elles surtout un grand danger pour les modèles de base de données relationnelles classiques et les informations qui y sont implémentées.
Qu’est-ce qu’une injection SQL ?
Sous le terme d’injection SQL, on comprend l’exploitation d’une faille de sécurité dans les systèmes de bases de données relationnelles qui se réfèrent au langage SQL. L’assaillant utilise les données saisies par l’utilisateur sur l’interface de la base de données qui ne sont pas suffisamment masquées et qui comportent des caractères méta (comme le double trait d’union, les guillemets, point-virgule etc.). Ces signes occupent des fonctions spécifiques pour l’interpréteur SQL et permettent d’influencer de manière externe des commandes exécutées. Souvent, une injection SQL survient avec des programmes PHP et ASP qui reposent sur des interfaces plus anciennes. Les données ne comportent alors pas toujours les masques nécessaires et constituent par conséquent une cible parfaite pour une attaque.
En utilisant de manière ciblée les caractères de fonction, un utilisateur non autorisé peut s’infiltrer dans les commandes SQL et manipuler les saisies de sorte de changer les données, les supprimer ou les lire. Dans les cas les plus graves, il est même possible que l’attaquant réussisse de cette manière à accéder à des lignes d’instruction des systèmes d’exécution de commandes et ainsi à l’ensemble du serveur de base de données.
En utilisant de manière ciblée les caractères de fonction, un utilisateur non autorisé peut s’infiltrer dans les commandes SQL et manipuler les saisies de sorte de changer les données, les supprimer ou les lire. Dans les cas les plus graves, il est même possible que l’attaquant réussisse de cette manière à accéder à des lignes d’instruction des systèmes d’exécution de commandes et ainsi à l’ensemble du serveur de base de données.
Exemple d’injection SQL : comment fonctionne les attaques de base de données ?
Les serveurs de bases de données fragiles étant facilement repérables et les attaques par injection SQL tout aussi simples, les attaquants du monde entier s’emparent très souvent de cette méthode. Les attaquants agissent ainsi selon différents modèles et exploitent des failles nouvelles des processus de management de données des applications mais surtout celles qui leur sont plus familières. Pour vous expliquer comment une injection SQL fonctionne, nous avons répertorié les méthodes les plus fréquentes à titre d’exemple.
Exemple 1 : accès grâce à une saisie utilisateur insuffisamment masquée
Afin d’accéder à une base de données, un utilisateur doit tout d’abord s’authentifier. A cette fin, des scripts existent qui présentent par exemple un formulaire login avec nom d’utilisateur et mot de passe. L’utilisateur remplit le formulaire et le script vérifie si une entrée correspondante existe dans la base de données. En règle générale, les bases de données se présentent sous forme de tableau avec les colonnes « utilisateur » ainsi que « nom d’utilisateur » et « mot de passe ». Pour importe l’application Web, les lignes de script (pseudo-code) pour l’accès au serveur Web peuvent être les suivantes :
Exemple 1 : accès grâce à une saisie utilisateur insuffisamment masquée
Afin d’accéder à une base de données, un utilisateur doit tout d’abord s’authentifier. A cette fin, des scripts existent qui présentent par exemple un formulaire login avec nom d’utilisateur et mot de passe. L’utilisateur remplit le formulaire et le script vérifie si une entrée correspondante existe dans la base de données. En règle générale, les bases de données se présentent sous forme de tableau avec les colonnes « utilisateur » ainsi que « nom d’utilisateur » et « mot de passe ». Pour importe l’application Web, les lignes de script (pseudo-code) pour l’accès au serveur Web peuvent être les suivantes :
uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
Un attaquant a maintenant la possibilité de manipuler le champ relatif au mot de passe grâce à une injection SQL, en entrant par exemple password' OR 1='1, ce qui mène à la requête SQL suivante :
sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'
En agissant ainsi, il peut accéder à l’ensemble des tables utilisateurs de la base de données, le mot de passe étant toujours valide (1='1'). S’il se connecte en tant qu’administrateur, il peut procéder à toutes les modifications qu’il désire. Autrement, c’est le champ « nom d’utilisateur » qui peut être également manipulé de cette manière.
Exemple 2 : espionnage informatique par manipulation d’ID
Solliciter des informations sur une base de données par authentification ID est une méthode courante. Toutefois, cela constitue une porte d’entrée pour les injections SQL. Un serveur Web sait alors par exemple grâce aux données ID transmises par l’URL quelles informations de la base de données il doit appeler. Le script PHP correspondant ressemble au suivant :
Exemple 2 : espionnage informatique par manipulation d’ID
Solliciter des informations sur une base de données par authentification ID est une méthode courante. Toutefois, cela constitue une porte d’entrée pour les injections SQL. Un serveur Web sait alors par exemple grâce aux données ID transmises par l’URL quelles informations de la base de données il doit appeler. Le script PHP correspondant ressemble au suivant :
<?php
#Requete sur base de donnees a l aide d’une ID
$id = $_REQUEST['id'];
$result = mysql_query("SELECT * from tabelle WHERE id=$id");
# Affichage du résultat ...
?>
L’URL attendu a la forme …/script.php?id=22. Dans ce cas précis, l’entrée du tableau est appelée avec l’ID 22. Si un utilisateur externe a l’occasion de manipuler l’URL demandée et envoie à la place au serveur Web la requête …/script.php?id=22+or+1=1, le résultat mysql_query appelé ne va pas seulement lire l’entrée avec l’ID 22, mais toutes les données.
SELECT * FROM tabelle WHERE id=22 or 1=1
Comment les attaquants trouvent des systèmes de bases de données vulnérables ?
En principe, chaque site Web et application Web peut être vulnérable à une injection SQL. Il suffit que le langage de la base de données soit SQL. En effet, trop souvent, les fabricants des programmes ne mettent pas en place un niveau de sécurité suffisant. Les failles découvertes ne restent pas longtemps secrètes dans le monde du Net. Il existe par exemple des pages d’informations qui présentent les failles de sécurité et dévoilent immédiatement aux criminels comment trouver le projet Web par une recherche Google. Grâce à des rapports d’erreurs classiques, il peut être rapidement vérifié si les références indiquées représentent en effet une cible d’attaque potentielle. Ainsi, il est possible d’ajouter simplement une apostrophe à une URL qui contient le paramètre d’ID, comme dans l’exemple suivant :
[Domainname].de/news.php?id=5‘
Un site Web vulnérable va alors renvoyer un message d’erreur, qui ressemblera dans ses grandes lignes au suivant :
„Query failed: You have an error in your SQL Syntax …“
En français : « Demande échouée : votre syntaxe SQL comporte une erreur ». Avec une telle méthode, peuvent être également émis le nombre de colonnes, les noms des tables ou colonnes, les versions SQL ou même les noms utilisateurs et mots de passe. De plus, la plupart des outils libres permettent d’automatiser les recherches et les injections SQL. Dans tous les cas, l’exploitation de failles de sécurité connue est passible d’une sanction dès lors qu’il ne s’agit pas de systèmes de banques de données qui vous appartiennent ou de systèmes que l’on vous a soumis pour vérifier la sécurité.
„Query failed: You have an error in your SQL Syntax …“
En français : « Demande échouée : votre syntaxe SQL comporte une erreur ». Avec une telle méthode, peuvent être également émis le nombre de colonnes, les noms des tables ou colonnes, les versions SQL ou même les noms utilisateurs et mots de passe. De plus, la plupart des outils libres permettent d’automatiser les recherches et les injections SQL. Dans tous les cas, l’exploitation de failles de sécurité connue est passible d’une sanction dès lors qu’il ne s’agit pas de systèmes de banques de données qui vous appartiennent ou de systèmes que l’on vous a soumis pour vérifier la sécurité.
Comment protéger vos bases de données d’une injection SQL ?
Vous pouvez mettre en place différentes mesures pour empêcher une attaque par injection SQL de votre système de base de données. Par conséquent, il est nécessaire de s’attarder sur l’ensemble des composants impliqués, tels que le serveur, sur chacune des applications mais aussi sur les systèmes de management de base de données.
Etape 1 : superviser la saisie automatique des applications
Testez et filtrez les méthodes et paramètres utilisés par les applications connectées lors de vos saisies dans la base de données. Les données remises devraient toujours être en accord avec le type de données attendu. Si un paramètre numérique est requis, vous pouvez le vérifier avec un script PHP à l’aide de la fonction is_numeric(). En ce qui concerne les filtres, les caractères spéciaux correspondants sont ignorés. Un autre point important est de s’assurer que les applications n’émettent pas de messages d’erreurs externes qui indiquent des informations sur le système utilisé ou les structures de la base de données.
Entre-temps, d’autres pratiques se sont largement répandues, comme ce que l’on nomme les Prepared Statements qui peuvent être utilisées avec de nombreux systèmes de management de banques de données. Ces déclarations prédéfinies étaient à l’origine utilisées pour exécuter des requêtes fréquentes, mais du fait de leur structure, elles permettent également de réduire les risques d’injection SQL. En effet, les instructions paramétrées transfèrent la commande SQL réelle des paramètres séparément de la base de données. Seul le système de management de la base de données lui-même relie les deux et masque ainsi automatiquement les caractères spéciaux importants.
Etape 2 : s’assurer de la protection complète du serveur
La sécurité du serveur sur lequel a cours votre système de gestion de base de données joue naturellement un rôle primordial dans la prévention contre les injections SQL. La première étape est donc de renforcer votre système d’exploitation selon le schéma établi :
Etape 3 : renforcer les bases de données et utiliser des codes sûrs
Tout comme votre système d’exploitation, les bases de données doivent être débarrassées de tout élément inutile et être mises à jour régulièrement. A cette fin, éliminez toutes les procédures enregistrées dont vous n’avez pas besoin et désactivez tous les services et comptes utilisateurs inutiles. Installez un compte spécifique pour votre base de données, qui sera prévu simplement pour y accéder depuis le Web, et qui sera pourvu de droits d’accès très restreints. Enregistrez par ailleurs dans votre base de données toutes les données sensibles de manière chiffrée, comme par exemple vos mots de passe.
Pour les Prepared Statements, il est urgemment recommandé de ne pas utiliser le module PHP mysql mais de choisir à la place mysqli ou PDO. De cette manière, vous pourrez par la même occasion vous protéger avec des codes sûrs. La fonction mysqli_real_escape_string() du script PHP permet par exemple d’éviter d’émettre des caractères spéciaux aux bases de données SQL sous leur forme originale. Si vous prenez par exemple les lignes de code ci-dessous :
Etape 1 : superviser la saisie automatique des applications
Testez et filtrez les méthodes et paramètres utilisés par les applications connectées lors de vos saisies dans la base de données. Les données remises devraient toujours être en accord avec le type de données attendu. Si un paramètre numérique est requis, vous pouvez le vérifier avec un script PHP à l’aide de la fonction is_numeric(). En ce qui concerne les filtres, les caractères spéciaux correspondants sont ignorés. Un autre point important est de s’assurer que les applications n’émettent pas de messages d’erreurs externes qui indiquent des informations sur le système utilisé ou les structures de la base de données.
Entre-temps, d’autres pratiques se sont largement répandues, comme ce que l’on nomme les Prepared Statements qui peuvent être utilisées avec de nombreux systèmes de management de banques de données. Ces déclarations prédéfinies étaient à l’origine utilisées pour exécuter des requêtes fréquentes, mais du fait de leur structure, elles permettent également de réduire les risques d’injection SQL. En effet, les instructions paramétrées transfèrent la commande SQL réelle des paramètres séparément de la base de données. Seul le système de management de la base de données lui-même relie les deux et masque ainsi automatiquement les caractères spéciaux importants.
Etape 2 : s’assurer de la protection complète du serveur
La sécurité du serveur sur lequel a cours votre système de gestion de base de données joue naturellement un rôle primordial dans la prévention contre les injections SQL. La première étape est donc de renforcer votre système d’exploitation selon le schéma établi :
- Installer ou activer seulement les applications et services qui sont pertinents pour faire fonctionner la base de données.
- Supprimer tous les comptes utilisateurs qui ne sont pas nécessaires.
- S’assurer que toutes les mises à jour des systèmes et programmes pertinents sont installés.
Etape 3 : renforcer les bases de données et utiliser des codes sûrs
Tout comme votre système d’exploitation, les bases de données doivent être débarrassées de tout élément inutile et être mises à jour régulièrement. A cette fin, éliminez toutes les procédures enregistrées dont vous n’avez pas besoin et désactivez tous les services et comptes utilisateurs inutiles. Installez un compte spécifique pour votre base de données, qui sera prévu simplement pour y accéder depuis le Web, et qui sera pourvu de droits d’accès très restreints. Enregistrez par ailleurs dans votre base de données toutes les données sensibles de manière chiffrée, comme par exemple vos mots de passe.
Pour les Prepared Statements, il est urgemment recommandé de ne pas utiliser le module PHP mysql mais de choisir à la place mysqli ou PDO. De cette manière, vous pourrez par la même occasion vous protéger avec des codes sûrs. La fonction mysqli_real_escape_string() du script PHP permet par exemple d’éviter d’émettre des caractères spéciaux aux bases de données SQL sous leur forme originale. Si vous prenez par exemple les lignes de code ci-dessous :
$query = "SELECT * FROM users
WHERE username= '" . $_POST['username'] . "'
AND password= '" . $_POST['password'] . "'";
Pour les rallonger avec la fonction mentionnée :
$query = "SELECT * FROM users
WHERE username= '" . mysqli_real_escape_string($_POST['username']) . "'
AND password= '" . mysql_real_escape_string($_POST['password']) . "'";
Les caractères problématiques des données utilisateurs seront remplacés par la variante SQL sécurisée (\').
Quel rapport entre Bobby Tables et les injections SQL ?
Sur la page bobby-tables.com, la bande dessinée xkcd s’intéresse au problème des saisies utilisateurs dans les bases de données. La bande dessinée montre une mère, qui reçoit un coup de fil de l’école de son fils (appelé affectueusement le petit Bobby Tables). Elle confirme que son fils s’appelle bel et bien Robert'); DROP TABLE Students;– – et comprend la raison de l’appel : après que l’école a essayé de créer une entrée pour Robert dans la base de données, l’ensemble des données existantes ont été effacées. La mère de Robert ne s’en émeut pas mais exprime plutôt son souhait que l’école apprenne de cette erreur et assainisse à l’avenir les entrées de leurs bases de données. La BD cherche à illustrer les conséquences terribles que des saisies utilisateurs non vérifiées peuvent avoir dans une base de données. Dans le cas mentionné, l’injection SQL dans la base de données de l’école s’est déroulée comme ci-dessous. Les noms des étudiants sont enregistrés dans une table nommée Students. Dès qu’un nouvel étudiant arrive dans l’école, il est entré dans cette table. Le code correspondant va alors ressembler au suivant :
$sql = "INSERT INTO Students (Name) VALUES ('" . $studentName . "');";
execute_sql($sql);
Il s’agit ici d’une commande SQL INSERT classique qui introduit le contenu de la variable $studentName dans la table Students. Dans la deuxième partie du code, l’instruction est communiquée à la base de données (execute_sql). Pour un étudiant nommé Paul, l’instruction SQL fonctionne tout aussi bien.
INSERT INTO Students (Name) VALUES ('Paul');
Le code ci-dessus permet d’introduire l’élève Paul dans la table Students. Maintenant, la même instruction doit introduire bobby tables, ce qui mène au code suivant :
INSERT INTO Students (Name) VALUES ('Robert'); DROP TABLE Students;––');
L’entrée est certes insérée dans la table, mais l’instruction DROP TABLE intégrée dans le nom s’est arrangée pour que la table globale qui y était associée soit effacée. Grâce aux données non masquées de la base de données, l’injection SQL de la mère, qui se cache derrière le nom du petit Bobby tables, a pu aboutir.
La solution apportée dans la BD d’assainir le code n’est toutefois pas tout à fait recommandable. Le masque manuel des caractères étant en effet susceptible de bugger, il est préférable d’utiliser les fonctions présentées précédemment, comme les instructions paramétrées ou la fonction masquée mysqli_real_escape_string() pour protéger votre base de données des attaques perfides et éviter une injection SQL.
La solution apportée dans la BD d’assainir le code n’est toutefois pas tout à fait recommandable. Le masque manuel des caractères étant en effet susceptible de bugger, il est préférable d’utiliser les fonctions présentées précédemment, comme les instructions paramétrées ou la fonction masquée mysqli_real_escape_string() pour protéger votre base de données des attaques perfides et éviter une injection SQL.