Comment prévenir les injections SQL en PHP

Publié 26 janvier 2026

L'injection SQL est un problème de sécurité majeur qui peut permettre aux attaquants de modifier les requêtes de base de données et d'accéder à des données privées auxquelles ils ne devraient pas avoir accès. Dans les applications PHP, les attaques par injection SQL se produisent lorsque les données saisies par l'utilisateur ne sont pas vérifiées ou nettoyées avant d'être utilisées dans les requêtes SQL. Cet article examine les différents types d'attaques par injection SQL, montre des exemples de code PHP vulnérable et présente les meilleures méthodes pour prévenir les problèmes d'injection SQL dans vos applications.

Comprendre les Attaques par Injection SQL en PHP

Qu'est-ce qu'une Injection SQL ?

L'injection SQL est une technique utilisée par les attaquants pour injecter du code SQL malveillant dans une application PHP vulnérable. Elle se produit lorsque les données saisies par l'utilisateur ne sont pas correctement validées ou nettoyées avant d'être utilisées dans une requête SQL. Les attaquants exploitent une validation d'entrée inadéquate et des requêtes SQL dynamiques pour manipuler le comportement de l'application et obtenir un accès non autorisé à la base de données. En créant des valeurs d'entrée spéciales, les attaquants peuvent modifier la structure de la requête SQL, contourner l'authentification, récupérer des informations sensibles ou même exécuter des commandes SQL arbitraires sur le serveur de base de données.

Voici un exemple d'extrait de code PHP vulnérable qui permet l'injection SQL :

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

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($connection, $query);

Dans ce code, les valeurs $username et $password fournies par l'utilisateur sont directement concaténées dans la requête SQL sans aucune validation ou nettoyage. Un attaquant peut exploiter cette vulnérabilité en saisissant une entrée malveillante comme :

username: admin' --
password: n'importe quelle valeur

La requête SQL résultante serait :

SELECT * FROM users WHERE username='admin' -- AND password='n'importe quelle valeur'

La syntaxe de commentaire -- annule la vérification du mot de passe, permettant à l'attaquant de contourner l'authentification et d'obtenir un accès non autorisé.

Types Courants d'Attaques par Injection SQL

Injection SQL Basée sur UNION

Dans ce type d'attaque, les attaquants utilisent l'opérateur UNION pour combiner les résultats de plusieurs instructions SELECT et extraire des informations sensibles. En ajoutant une instruction SELECT malveillante à la requête originale, les attaquants peuvent récupérer des données de différentes tables ou colonnes qui n'étaient pas destinées à être accessibles. Cette technique repose sur le fait que l'application renvoie le résultat de la requête injectée à l'attaquant.

Exemple d'injection SQL basée sur UNION :

Requête originale :

SELECT name, description FROM products WHERE id = $id

Requête injectée :

SELECT name, description FROM products WHERE id = 1 UNION SELECT username, password FROM users

La requête injectée combine la requête originale avec une instruction SELECT supplémentaire qui récupère les identifiants sensibles des utilisateurs depuis la table users.

Injection SQL en Aveugle

L'injection SQL en aveugle se produit lorsque l'application ne renvoie pas directement les résultats de la requête injectée à l'attaquant. Au lieu de cela, les attaquants envoient des requêtes ciblées et observent la réponse de l'application pour déduire la structure et le contenu de la base de données. Ils y parviennent en faisant en sorte que l'application se comporte différemment en fonction du résultat de la condition injectée. En construisant soigneusement les requêtes et en analysant le temps de réponse de l'application, les messages d'erreur ou d'autres changements de comportement, les attaquants peuvent extraire des informations sensibles bit par bit.

Exemple d'injection SQL en aveugle :

Requête originale :

SELECT * FROM users WHERE id = $id

Requête injectée :

SELECT * FROM users WHERE id = 1 AND SUBSTRING(password, 1, 1) = 'a'

Dans ce cas, l'attaquant injecte une condition qui vérifie si le premier caractère du mot de passe est 'a'. En observant la réponse de l'application (par exemple, un délai ou une erreur), l'attaquant peut déterminer si la condition est vraie ou fausse et deviner progressivement le mot de passe caractère par caractère.

Injection SQL Basée sur les Erreurs

Dans cette technique, les attaquants déclenchent intentionnellement des erreurs SQL pour recueillir des informations sur la base de données et son schéma. Ils le font en insérant des instructions SQL mal formées ou des caractères spéciaux qui provoquent la génération de messages d'erreur par la base de données. Ces messages d'erreur contiennent souvent des informations précieuses telles que les noms de tables, les noms de colonnes ou même des portions de données sensibles. Les attaquants peuvent utiliser ces informations pour affiner leurs attaques et cibler des parties spécifiques de la base de données.

Exemple d'injection SQL basée sur les erreurs :

Requête injectée :

SELECT * FROM users WHERE id = 1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT (SELECT CONCAT(username,':',password)) FROM users LIMIT 0,1), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)

Cette requête injectée complexe tente d'extraire le nom d'utilisateur et le mot de passe de la table users en déclenchant une erreur qui inclut les informations sensibles dans le message d'erreur.

Prévenir les Attaques par Injection SQL

Pour prévenir les attaques par injection SQL dans les applications PHP, suivez ces meilleures pratiques :

Pratique Description
Requêtes Paramétrées Utilisez des instructions préparées avec des requêtes paramétrées pour séparer les données utilisateur de la structure de la requête SQL.
Validation des Entrées Validez et nettoyez les données saisies par l'utilisateur avant de les utiliser dans les requêtes SQL. Rejetez ou nettoyez tous les caractères spéciaux ou entrées malveillantes.
Principe du Moindre Privilège Configurez le compte utilisateur de la base de données pour qu'il ait les privilèges minimaux requis pour les fonctionnalités de l'application.
Échappement des Caractères Spéciaux Si les requêtes paramétrées ne sont pas possibles, échappez correctement les caractères spéciaux dans les entrées utilisateur en utilisant mysqli_real_escape_string() de PHP ou la fonction quote() de PDO.
Procédures Stockées Utilisez des procédures stockées pour encapsuler les opérations de base de données et limiter l'accès aux tables sous-jacentes.
Instructions Préparées Utilisez des instructions préparées pour précompiler les requêtes SQL et séparer les entrées utilisateur de la structure de la requête. Les instructions préparées traitent les entrées utilisateur comme des données, et non comme une partie de la commande SQL.

Voici un diagramme illustrant le flux d'une attaque par injection SQL :

graph TD A[Attaquant] --> B[Crée une entrée malveillante] B --> C[Envoie l'entrée malveillante à l'application] C --> D[L'application reçoit l'entrée] D --> E{Entrée correctement validée/nettoyée ?} E -->|Non| F[Requête SQL concaténée avec l'entrée malveillante] F --> G[La base de données exécute la requête injectée] G --> H[L'attaquant obtient un accès non autorisé ou récupère des données sensibles] E -->|Oui| I[Requête SQL exécutée en toute sécurité] I --> J[L'application fonctionne comme prévu]

Prévenir l'Injection SQL en PHP avec les Instructions Préparées

Instructions Préparées

Les instructions préparées exécutent des requêtes SQL en séparant la logique SQL des entrées utilisateur. Elles protègent contre les attaques par injection SQL. Au lieu d'intégrer les entrées utilisateur dans la requête SQL, les instructions préparées utilisent des espaces réservés (par exemple, ? ou des paramètres nommés comme :username) pour les valeurs d'entrée. Ces espaces réservés sont ensuite liés aux valeurs réelles avant l'exécution de la requête. En séparant la structure SQL des entrées utilisateur, les instructions préparées traitent l'entrée comme des données et non comme une partie de la commande SQL, empêchant l'injection de code SQL malveillant.

PDO (PHP Data Objects) pour les Instructions Préparées

PDO (PHP Data Objects) est une extension en PHP qui fournit un moyen cohérent et sécurisé d'interagir avec différentes bases de données. Elle prend en charge les instructions préparées et les requêtes paramétrées, ce qui en fait un bon choix pour prévenir les vulnérabilités d'injection SQL.

PDO fournit des méthodes pour préparer, lier et exécuter des instructions SQL de manière sécurisée. Elle gère les différences entre les fournisseurs de bases de données, vous permettant d'écrire du code portable et maintenable. PDO offre également de meilleures performances que les fonctions MySQL traditionnelles, car elle peut réutiliser la même instruction préparée plusieurs fois avec différentes valeurs de paramètres.

Guide d'Utilisation des Instructions Préparées avec PDO

Voici un guide sur la façon d'utiliser les instructions préparées avec PDO pour prévenir l'injection SQL en PHP :

  1. Connectez-vous à la base de données avec PDO :

    $dsn = 'mysql:host=localhost;dbname=mydatabase';
    $username = 'votre_nom_utilisateur';
    $password = 'votre_mot_de_passe';
    
    try {
       $pdo = new PDO($dsn, $username, $password);
       $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
       die('Échec de la connexion : ' . $e->getMessage());
    }
  2. Préparez la requête SQL avec des espaces réservés pour les entrées utilisateur :

    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");

    Dans cet exemple, :username est un paramètre nommé qui sert d'espace réservé pour la valeur du nom d'utilisateur fournie par l'utilisateur.

  3. Liez les entrées utilisateur aux espaces réservés :

    $username = $_POST['username'];
    $stmt->bindParam(':username', $username);

    La méthode bindParam() lie l'entrée utilisateur au paramètre nommé dans l'instruction préparée. Elle traite l'entrée comme des données et non comme une partie de la commande SQL.

  4. Exécutez l'instruction préparée :

    $stmt->execute();

    La méthode execute() exécute l'instruction préparée avec les valeurs de paramètres liées.

  5. Récupérez et traitez les résultats :

    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    La méthode fetch() récupère le résultat de l'instruction exécutée. Vous pouvez spécifier le style de récupération, tel que PDO::FETCH_ASSOC, pour retourner un tableau associatif.

Exemple: Connexion Utilisateur

Considérons un exemple de système de connexion utilisateur où nous devons obtenir les informations de l'utilisateur en fonction du nom d'utilisateur et du mot de passe fournis. Voici comment vous pouvez utiliser les instructions préparées avec PDO pour prévenir l'injection SQL :

// Récupérez les entrées utilisateur du formulaire de connexion
$username = $_POST['username'];
$password = $_POST['password'];

// Préparez la requête SQL avec des espaces réservés
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");

// Liez les entrées utilisateur aux espaces réservés
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);

// Exécutez l'instruction préparée
$stmt->execute();

// Récupérez l'enregistrement utilisateur
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// Vérifiez si l'utilisateur existe et si le mot de passe est correct
if ($user) {
    // Utilisateur authentifié avec succès
    // Effectuez des actions (par exemple, définir une session, rediriger)
} else {
    // Nom d'utilisateur ou mot de passe invalide
    // Affichez un message d'erreur
}

Dans cet exemple, le nom d'utilisateur et le mot de passe fournis par l'utilisateur sont liés aux espaces réservés dans l'instruction préparée. La requête SQL est exécutée en toute sécurité, empêchant les tentatives potentielles d'injection SQL.

Avantages des Instructions Préparées

  • Sépare la logique SQL des entrées utilisateur, empêchant les attaques par injection SQL
  • Améliore la lisibilité et la maintenabilité du code
  • Permet de réutiliser la même instruction préparée avec différentes valeurs de paramètres
  • Offre de meilleures performances que les fonctions MySQL traditionnelles

Meilleures Pratiques

  • Utilisez toujours des instructions préparées pour les requêtes SQL avec des entrées utilisateur
  • Validez et nettoyez les entrées utilisateur avant de les utiliser dans les requêtes SQL
  • Utilisez des paramètres nommés pour la lisibilité et la maintenabilité
  • Mettez en œuvre une configuration de base de données sécurisée et un contrôle d'accès basé sur le moindre privilège
  • Maintenez votre logiciel PHP et de base de données à jour avec les derniers correctifs de sécurité

Mesures Supplémentaires pour Prévenir l'Injection SQL en PHP

Techniques de Validation des Entrées

En plus d'utiliser des instructions préparées, vous devez valider et nettoyer les entrées utilisateur avant de les utiliser dans les requêtes SQL. La validation des entrées prévient les attaques par injection SQL en vérifiant que les données saisies par les utilisateurs correspondent au format attendu et ne contiennent que des caractères autorisés.

La liste blanche est une bonne technique, où vous définissez un ensemble de caractères ou de motifs autorisés et rejetez toute entrée qui ne correspond pas à ces règles. Par exemple, si vous attendez qu'un utilisateur saisisse un identifiant numérique, vous pouvez valider l'entrée en utilisant des expressions régulières pour vérifier qu'elle ne contient que des chiffres.

Voici un exemple de validation d'un identifiant numérique en utilisant PHP :

$id = $_POST['id'];

// Validez que l'identifiant ne contient que des chiffres
if (!preg_match('/^\d+$/', $id)) {
    // Entrée invalide, gérez l'erreur
    die("Format d'identifiant invalide");
}

// Utilisez l'identifiant validé dans la requête SQL
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);

Dans cet exemple, la fonction preg_match() vérifie si la variable $id ne contient que des chiffres. Si la validation échoue, une erreur est déclenchée, empêchant l'entrée invalide d'être utilisée dans la requête SQL.

Vous devez également échapper correctement les caractères spéciaux et utiliser les bonnes méthodes d'encodage. Les caractères spéciaux comme les guillemets simples, les guillemets doubles et les barres obliques inverses peuvent être utilisés pour manipuler les requêtes SQL s'ils ne sont pas gérés correctement. En échappant ces caractères ou en utilisant des fonctions d'encodage appropriées, vous pouvez les empêcher d'être interprétés comme faisant partie de la syntaxe SQL.

PHP dispose de fonctions comme mysqli_real_escape_string() ou PDO::quote() pour échapper les caractères spéciaux dans les entrées utilisateur. Cependant, il est généralement préférable d'utiliser des instructions préparées avec des requêtes paramétrées plutôt que l'échappement manuel, car c'est une approche plus fiable et sécurisée.

Voici quelques exemples de techniques courantes de validation des entrées :

Technique Description Exemple
Liste Blanche Autoriser uniquement des caractères ou motifs spécifiques Autoriser uniquement les caractères alphanumériques : /^[a-zA-Z0-9]+$/
Validation de Longueur Vérifier la longueur de l'entrée S'assurer que le nom d'utilisateur comporte entre 4 et 20 caractères : /^.{4,20}$/
Validation de Type Valider le type d'entrée (par exemple, entier, email) Valider une adresse email : /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}$/i
Validation de Plage Vérifier si l'entrée se situe dans une plage spécifiée S'assurer que l'âge est entre 18 et 120 : /^(1[89]|[2-9]\d|1[01]\d|120)$/

Procédures Stockées et Requêtes Paramétrées

Les procédures stockées sont des instructions SQL précompilées qui sont stockées dans la base de données et peuvent être appelées depuis le code de l'application. Elles ajoutent une couche supplémentaire de sécurité en encapsulant une logique SQL complexe et en limitant l'accès aux tables sous-jacentes.

Lors de l'utilisation de procédures stockées, vous pouvez définir des paramètres qui acceptent les entrées utilisateur, de manière similaire aux instructions préparées. En passant les entrées utilisateur comme paramètres aux procédures stockées, vous pouvez prévenir les attaques par injection SQL puisque l'entrée est traitée comme des données et non comme une partie de la commande SQL.

Voici un exemple de procédure stockée qui récupère les informations utilisateur en fonction d'un nom d'utilisateur fourni :

CREATE PROCEDURE GetUserByUsername
    @Username VARCHAR(50)
AS
BEGIN
    SELECT * FROM users WHERE username = @Username
END

Dans cet exemple, la procédure stockée GetUserByUsername accepte un paramètre @Username et l'utilise dans la requête SQL pour récupérer les informations utilisateur. Le paramètre est traité comme des données, empêchant l'injection SQL.

Pour appeler la procédure stockée depuis PHP, vous pouvez utiliser PDO ou MySQLi :

$username = $_POST['username'];

$stmt = $pdo->prepare("CALL GetUserByUsername(?)");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

En utilisant des procédures stockées avec des requêtes paramétrées, vous pouvez centraliser la logique d'accès à la base de données, améliorer les performances et renforcer la sécurité de votre application PHP.

Principe du Moindre Privilège et Rôles Utilisateur de Base de Données

Le principe du moindre privilège signifie que les utilisateurs et les applications ne doivent avoir que les permissions minimales nécessaires pour accomplir leurs tâches. Appliquer ce principe aux rôles et permissions des utilisateurs de base de données est une étape importante pour prévenir l'injection SQL et réduire l'impact des attaques potentielles.

Lors de la configuration de l'utilisateur de base de données pour votre application PHP, limitez ses droits d'accès au minimum requis pour les fonctionnalités de l'application. Cela signifie donner à l'utilisateur uniquement les permissions nécessaires pour effectuer des tâches spécifiques, telles que SELECT, INSERT, UPDATE ou DELETE, sur les tables concernées.

En restreignant les privilèges de l'utilisateur de base de données, vous pouvez réduire les dommages potentiels qu'un attaquant peut causer s'il parvient à exploiter une vulnérabilité d'injection SQL. Par exemple, si l'application n'a besoin que d'un accès en lecture à certaines tables, l'utilisateur de base de données ne devrait se voir accorder que le privilège SELECT sur ces tables uniquement, empêchant toute modification ou suppression non autorisée.