Les in­jec­tions SQL re­pré­sen­tent une menace im­por­tante pour les modèles de bases de données re­la­tion­nelles et les in­for­ma­tions qui y sont stockées. Une pro­tec­tion complète contre ces accès externes non autorisés, rendus possibles par des failles de sécurité, est donc es­sen­tielle.

Qu’est-ce qu’une injection SQL ?

Une injection SQL est l’ex­ploi­ta­tion d’une faille de sécurité dans les systèmes de bases de données re­la­tion­nelles qui utilisent le langage de requête SQL pour la saisie de données. L’attaquant utilise des entrées uti­li­sa­teur non sé­cu­ri­sées et contenant des mé­ta­ca­rac­tères comme le double tiret, les guil­le­mets ou le point-virgule. Ces ca­rac­tères ont des fonctions spéciales pour l’in­ter­pré­teur SQL et per­met­tent d’in­fluen­cer de l’extérieur les commandes exécutées. L’injection SQL se produit souvent dans le cadre de pro­grammes PHP et ASP, basés sur des in­ter­faces obsolètes. Les entrées ne reçoivent alors parfois pas le masquage né­ces­saire et sont donc la cible parfaite pour une attaque.

Avec l’uti­li­sa­tion ciblée de ca­rac­tères de fonction, un uti­li­sa­teur non autorisé peut ainsi in­tro­duire d’autres commandes SQL et manipuler les entrées de telle sorte qu’il puisse modifier, effacer ou lire les données. Dans les cas les plus graves, il est même possible qu’un attaquant obtienne de cette manière l’accès à la ligne de commande du système exécutant les commandes et ainsi à l’ensemble du serveur de base de données.

Exemples d’in­jec­tions SQL : comment fonc­tion­nent les attaques de bases de données ?

Comme les serveurs de bases de données vul­né­rables sont ra­pi­de­ment détectés et que les attaques par injection SQL sont tout aussi faciles à exécuter, cette méthode est l’une des plus po­pu­laires. Les criminels utilisent dif­fé­rents modèles d’attaque et ex­ploi­tent aussi bien des failles récentes que des vul­né­ra­bi­li­tés connues depuis longtemps dans les ap­pli­ca­tions im­pli­quées dans la gestion des données. Pour illustrer le fonc­tion­ne­ment exact d’une injection SQL, voici deux méthodes typiques.

Exemple 1 : accès grâce à une saisie uti­li­sa­teur in­suf­fi­sam­ment masquée

Pour que les uti­li­sa­teurs puissent accéder à une base de données, ils doivent gé­né­ra­le­ment s’au­then­ti­fier. Il existe à cet effet des scripts qui pré­sen­tent par exemple un for­mu­laire de connexion composé d’un nom d’uti­li­sa­teur et d’un mot de passe. Les uti­li­sa­teurs rem­plis­sent le for­mu­laire et le script vérifie ensuite s’il existe des entrées cor­res­pon­dantes dans la base de données. Par défaut, une table nommée users est créée dans la base de données, avec les colonnes username et password. Pour une ap­pli­ca­tion Web quel­conque, les lignes de script con­cer­nées (pseudo-code Python) pour l’accès au serveur Web pour­raient ê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)
python

Un attaquant a main­te­nant la pos­si­bi­lité de manipuler spé­ci­fi­que­ment le champ mot de passe par injection SQL, en sai­sis­sant par exemple password’ OR 1=‘1, ce qui conduit à la requête SQL suivante :

sql = "SELECT id FROM users WHERE (username=‘admin’ AND password=‘‘) OR ‘1’=‘1’"
python

De cette façon, il obtient un accès complet à toute la table des uti­li­sa­teurs de la base de données, puisque le mot de passe est toujours vrai (1=‘1’). Désormais, s’il se connecte en tant qu’ad­mi­nis­tra­teur, il peut faire toutes les mo­di­fi­ca­tions qu’il souhaite sur les entrées. Le champ du nom d’uti­li­sa­teur est tout aussi vul­né­rable à ce type de ma­ni­pu­la­tion.

Exemple 2 : espionner des données en ma­ni­pu­lant l’ID

Sol­li­ci­ter des in­for­ma­tions sur une base de données par au­then­ti­fi­ca­tion ID est une méthode courante. Toutefois, cela constitue une porte d’entrée pour les in­jec­tions SQL. Un serveur Web peut alors savoir, grâce aux données ID trans­mises par l’URL, quelles in­for­ma­tions de la base de données il doit appeler. Le script PHP cor­res­pon­dant ressemble au suivant :

<?php
    $mysqli = new mysqli("localhost", "user", "password", "database");
    $id = intval($_GET[‘id’]);
    $result = $mysqli->query("SELECT * FROM table WHERE id=$id");
    while ($row = $result->fetch_assoc()) {
        echo print_r($row, true);
    }
php

L’URL attendue est de la forme .../script.php?id=22. Dans ce cas précis, l’entrée de la table avec l’ID « 22 » sera appelée. Si un uti­li­sa­teur externe a l’occasion de manipuler l’URL demandée et envoie au serveur Web la requête .../script.php?id=22+OR+1=1, l’appel qui en résulte entraîne la lecture de toutes les données de la table :

SELECT * FROM table WHERE id=22 OR 1=1;
sql

Comment les criminels trouvent-ils des systèmes de bases de données vul­né­rables ?

De manière générale, tous les sites et ap­pli­ca­tions Web qui utilisent des bases de données SQL sans requêtes préparées (Prepared Sta­te­ments) ou autres mé­ca­nismes de pro­tec­tion peuvent être vul­né­rables aux in­jec­tions SQL. Les vul­né­ra­bi­li­tés dé­cou­vertes ne restent pas longtemps secrètes dans l’immensité d’Internet ! Il existe notamment des sites d’in­for­ma­tion qui pré­sen­tent les failles de sécurité actuelles et révèlent aux criminels comment trouver les projets Web vul­né­rables grâce à une simple recherche Google. Si un site Web renvoie des messages d’erreur SQL détaillés, les criminels peuvent les utiliser pour iden­ti­fier des vul­né­ra­bi­li­tés po­ten­tielles. Il suffit d’ajouter une apos­trophe à une URL contenant un paramètre d’ID, comme ici :

[Domainname].fr/news.php?id=5’

Un site Web vul­né­rable renverra alors un message d’erreur similaire à celui-ci :

Query failed: You have an error in your SQL syntax...

Soit en français : « Échec de la requête : Votre syntaxe SQL comporte une erreur… ». Des méthodes si­mi­laires per­met­tent également d’afficher le nombre de colonnes, les noms des tables et des colonnes, la version SQL ou même les noms d’uti­li­sa­teur et les mots de passe. Divers outils per­met­tent en outre d’au­to­ma­ti­ser la recherche de vul­né­ra­bi­li­tés et l’exécution d’in­jec­tions SQL.

Comment protéger votre base de données contre les in­jec­tions SQL ?

Vous pouvez prendre dif­fé­rentes mesures pour empêcher les attaques par injection SQL sur votre système de base de données. Pour ce faire, vous devez examiner l’ensemble des com­po­sants impliqués : le serveur, les dif­fé­rentes ap­pli­ca­tions ainsi que le système de gestion de base de données.

Étape 1 : sur­veil­ler les entrées au­to­ma­tiques des ap­pli­ca­tions

Lors du trai­te­ment des entrées par des ap­pli­ca­tions externes ou intégrées, il est essentiel de valider et de filtrer les valeurs trans­mises afin d’éviter les in­jec­tions SQL.

1. Vérifier les types de données

Chaque entrée doit cor­res­pondre au type de données attendu. Par exemple, si une entrée numérique est requise, une va­li­da­tion simple en PHP peut res­sem­bler à ceci :

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Entrée invalide");
}
php

Des contrôles si­mi­laires doivent être im­plé­men­tés pour les chaînes de ca­rac­tères, les valeurs de date ou d’autres formats spé­ci­fiques.

2. Filtrer les ca­rac­tères spéciaux

Les ca­rac­tères spéciaux peuvent être à l’origine de failles de sécurité, en par­ti­cu­lier dans les contextes SQL ou HTML. Une méthode sûre est d’utiliser htmlspecialchars() pour les entrées HTML et PDO::quote() pour les requêtes SQL.

3. Éviter les messages d’erreur

Les messages d’erreur directs dé­tail­lant des in­for­ma­tions tech­niques sur la base de données ou le système doivent être évités. Au lieu de cela, il est re­com­mandé d’utiliser une sortie générique comme :

echo "Une erreur est survenue. Veuillez réessayer plus tard.";
error_log("Une erreur inattendue s’est produite. Pour plus de détails, consultez le journal système.");
php

4. Utiliser des requêtes préparées

Un moyen sûr d’éviter les in­jec­tions SQL est d’utiliser les requêtes préparées men­tion­nées pré­cé­dem­ment. Avec cette approche, les commandes SQL et les pa­ra­mètres sont traités sé­pa­ré­ment, ce qui empêche l’exécution de code mal­veil­lant. Voici un exemple adapté en PHP avec PDO (PHP Data Objects) :

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(‘:id’, $user_id, PDO::PARAM_INT);
$stmt->execute();
php

Le système de gestion de base de données assure au­to­ma­ti­que­ment le trai­te­ment sécurisé des entrées.

Étape 2 : assurer une pro­tec­tion complète du serveur

La sécurité du serveur sur lequel vous exécutez votre système de gestion de base de données joue un rôle important dans la pré­ven­tion des in­jec­tions SQL. La première étape consiste à renforcer votre système d’ex­ploi­ta­tion selon le schéma établi :

  • Installez ou activez uni­que­ment les ap­pli­ca­tions et les services per­ti­nents pour le fonc­tion­ne­ment de la base de données.
  • Supprimez tous les comptes uti­li­sa­teurs dont vous n’avez pas besoin.
  • Assurez-vous que toutes les mises à jour per­ti­nentes du système et des pro­grammes sont ins­tal­lées.
  • Appliquez le principe du moindre privilège afin de vous assurer que les uti­li­sa­teurs et les services ne disposent que des au­to­ri­sa­tions stric­te­ment né­ces­saires.

En fonction des exigences de sécurité de votre projet Web, des mesures de pro­tec­tion sup­plé­men­taires peuvent être en­vi­sa­gées :

  • Systèmes de détection d’intrusion (IDS) et systèmes de pré­ven­tion d’intrusion (IPS) : ces systèmes utilisent dif­fé­rentes méthodes de détection pour détecter les attaques sur le serveur à un stade précoce, émettre des alertes et, dans le cas de l’IPS, dé­clen­cher au­to­ma­ti­que­ment les contre-mesures ap­pro­priées.
  • Ap­pli­ca­tion Layer Gateway (ALG) : un ALG surveille et filtre le trafic entre les ap­pli­ca­tions et les na­vi­ga­teurs Web di­rec­te­ment au niveau de l’ap­pli­ca­tion.
  • Web Ap­pli­ca­tion Firewall (WAF) : un WAF protège les ap­pli­ca­tions Web de manière ciblée contre l’injection SQL et le Cross-Site-Scripting (XSS) en bloquant ou en neu­tra­li­sant les requêtes suspectes.
  • Approche Zero Trust : cette approche moderne de la sécurité garantit que chaque accès, quelle que soit sa source, est contrôlé et vérifié avant d’être autorisé.
  • Ré­gle­men­ta­tion du pare-feu et seg­men­ta­tion du réseau : ces mesures sont es­sen­tielles pour minimiser la surface d’attaque à long terme.
  • Audits réguliers de sécurité in­for­ma­tique et tests d’intrusion : ils per­met­tent d’iden­ti­fier et de combler les vul­né­ra­bi­li­tés à un stade précoce.

Étape 3 : durcir la base de données et utiliser des codes sécurisés

Tout comme votre système d’ex­ploi­ta­tion, la base de données doit être dé­bar­ras­sée de tous les éléments superflus et mise à jour ré­gu­liè­re­ment. Supprimez toutes les pro­cé­dures stockées inu­ti­li­sées et dé­sac­ti­vez tous les services et comptes uti­li­sa­teurs inutiles. Créez un compte dédié à la base de données, ex­clu­si­ve­ment réservé à un accès Web, avec des droits d’accès limités.

Dans l’esprit des requêtes préparées, il est fortement re­com­mandé de ne pas utiliser le module PHP mysql (supprimé depuis PHP 7) et de choisir à la place mysqli ou PDO. Ces al­ter­na­tives per­met­tent d’écrire du code plus sécurisé. Voici un exemple de requête sécurisée avec mysqli :

$mysqli = new mysqli("localhost", "user", "password", "database");
if ($mysqli->connect_error) die("Echec de la connexion");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST[‘username’]);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST[‘password’], $hashedPassword)) {
    echo "Connexion réussie";
} else {
    echo "Identifiants de connexion erronés";
}
$stmt->close();
$mysqli->close();
php

De plus, les mots de passe ne doivent jamais être stockés di­rec­te­ment dans une base de données ou demandés en texte brut. Utilisez plutôt une méthode de hachage comme password_hash() en com­bi­nai­son avec password_verify() pour les protéger ef­fi­ca­ce­ment, par exemple de la manière suivante :

$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST[‘username’]);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST[‘password’], $row[‘password’])) {
    echo "Connexion réussie !";
} else {
    echo "Nom d’utilisateur ou mot de passe erroné.";
}
php

Bobby Tables : les in­jec­tions SQL ex­pli­quées en BD

Sur le site bobby-tables.com, la bande dessinée xkcd aborde le problème des entrées uti­li­sa­teur non sé­cu­ri­sées dans les bases de données. Elle met en scène une mère qui reçoit un coup de fil de l’école de son fils (af­fec­tueu­se­ment surnommé 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 ait essayé de créer une entrée pour Robert dans la base de données, toutes les données exis­tantes 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 prenne des mesures pour assainir les entrées de leurs bases de données à l’avenir.

La bande dessinée illustre clai­re­ment les con­sé­quences dra­ma­tiques que peuvent avoir les entrées non vérifiées des uti­li­sa­teurs dans les bases de données.

Aller au menu principal