Qu'est-ce qu'une injection SQL ?

L’injection SQL est une faille de sécurité qui se produit lorsqu’une application web envoie des données fournies par l’utilisateur (depuis un formulaire ou un champ de recherche par exemple) dans une requête SQL sans les vérifier ni les filtrer.

La base de données exécute alors ce qui est envoyé dans la requête tel quel (pour elle c'est un code qu'elle comprend, du SQL), même si un utilisateur malveillant a mis du texte destiné à la modifier.

Concrètement, si un site n’est pas protégé, un attaquant peut mettre du code SQL dans un champ prévu pour un simple texte. Le serveur web ne s’en rend pas compte et exécute ce code.

Une fois le code malveillant injecté, l'attaquant peut faire ce qu'il veut avec la base de données ; récupérer des données, supprimer des données, supprimer des tables ou même supprimer la base elle-même.

Prennons un exemple simple et concret ! Imaginons le formulaire suivant, d'une page web, qui propose à son utilisateur de se connecter :


<!DOCTYPE html> 
<html> 
    <head> 
        <title>Connexion</title> 
    </head> 

    <body> 
        <h2>Connexion</h2> 

        <form action="login.php" method="POST"> 
            <label>Login :</label> 
            <input type="text" name="login"><br><br> 

            <label>Mot de passe :</label> 
            <input type="password" name="password"><br><br> 

            <button type="submit">Se connecter</button> 
        </form> 

    </body> 
</html> 
            

Maintenant, examinons le code PHP vulnérable qui traite ces données :


<?php 

$pdo = new PDO("mysql:host=localhost;dbname=test", "ID", "password"); 

$login = $_POST['login']; 
$password = $_POST['password']; 

// Vulnérabilité du code : 
$sql = "SELECT * FROM users WHERE login = '$login' AND password = '$password'"; 

$result = $pdo->query($sql); 

if ($result && $result->rowCount() > 0) { 
        echo "Connexion réussie !"; 
    } else { 
        echo "Echec de connexion."; 
} 
?> 
            

Dans ce code, le site prend ce que l’utilisateur tape dans le formulaire ($login et $password) et le met directement dans la requête SQL.

Le problème ici, c’est qu'on fait trop confiance à ce que l’utilisateur envoie. On suppose que l’utilisateur va taper un vrai mot de passe (sous forme de chaîne de caractères)... Alors qu’en réalité, un attaquant peut taper n’importe quoi, comme du code SQL et tromper la requête. Comme ces valeurs sont simplement collées dans la commande SQL, la base de données ne fait pas la différence entre : un vrai mot de passe, ou du code qui change complètement la requête.

Exemple d'attaque :
(ce code est à but pédagogique et ne doit en aucun cas servir à compromettre un système)


// Login :
Admin

// Password :
' OR '1'='1

// Le SQL devient :
SELECT * 
FROM users 
WHERE login = 'Admin' AND password = '' OR '1'='1';
            

Explication :

Dans cette requête, la fin OR '1'='1' pose problème, car pour la base de données, cette condition est toujours vraie (1 est toujours égal à 1). De la même manière que le vrai mot de passe de l'utilisateur est une condition vrai pour la base de données, cette condition "1 = 1" est aussi vraie. La base de données "ne voit pas d'erreur", donc, elle accepte la connection.

Comment sécuriser la base de données ?

Nous allons utiliser une requête préparée. Ce type de requête SQL sépare la commande SQL des données fournies par les utilisateurs. Ainsi, la base de données ne peut pas interpréter ces données comme du code, puisqu’elles ne sont plus directement intégrées dans la requête SQL.


prepare("
    SELECT * 
    FROM users 
    WHERE login = :login AND password = :password
");

$stmt->execute([
    ':login' => $login,
    ':password' => $password
]);

if ($stmt->rowCount() > 0) {
    echo "Connexion réussie !";
} else {
    echo "Échec de connexion.";
}
?>
            

Maintenant, même si un attaquant tente d’injecter du code SQL, ce sera traité comme une simple chaîne de caractères par notre base de données. Et donc cela renverra bien le message d'erreur : "Echec de connexion".
L’injection SQL devient impossible.

Moralité : en cybersécurité, on ne fait jamais confiance aux données utilisateur.