Les injections SQL font partie des failles de sécurité les plus connues sur le web, mais elles restent encore très présentes sur de nombreux sites PHP. Le problème est simple : lorsqu’une application insère directement des données saisies par un utilisateur dans une requête SQL, un attaquant peut modifier cette requête pour lire, contourner ou détruire des données. En pratique, cela peut permettre de se connecter sans mot de passe, d’extraire des informations clients, de modifier des contenus ou, dans certains cas, de prendre le contrôle d’une partie du système.
Pour une entreprise, les conséquences peuvent être lourdes : perte de données, fuite d’informations sensibles, indisponibilité du site, atteinte à l’image de marque et problèmes de conformité. La bonne nouvelle, c’est qu’éviter les injections SQL sur un site PHP repose sur des méthodes claires, éprouvées et accessibles. Il ne s’agit pas seulement d’ajouter une fonction de filtrage, mais d’adopter de bonnes pratiques de développement dès la conception du projet, notamment lors de la création d’un site internet professionnel à Chartres pensé pour durer, évoluer et rester fiable.
Qu’est-ce qu’une injection SQL sur un site PHP ?
Une injection SQL se produit lorsqu’une donnée contrôlée par l’utilisateur est interprétée comme une partie de la requête SQL. Cela arrive souvent dans les formulaires de connexion, les moteurs de recherche internes, les filtres de catalogue, les paramètres d’URL ou encore les espaces d’administration.
Voici un exemple classique de code vulnérable :
$email = $_POST['email'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE email = '$email' AND password = '$password'";
$result = mysqli_query($conn, $sql);
Si un utilisateur malveillant saisit une valeur spécialement construite, il peut transformer le sens de la requête. Par exemple, au lieu de fournir un mot de passe réel, il peut tenter d’injecter une condition toujours vraie. Le serveur exécute alors une requête différente de celle attendue.
Ce risque concerne surtout les applications PHP qui construisent des requêtes SQL par concaténation de chaînes. Le problème ne vient pas de PHP en lui-même, mais de la manière dont les données sont manipulées avant d’être envoyées à MySQL, MariaDB ou un autre moteur de base de données.
Pourquoi cette faille est encore fréquente
Beaucoup de sites vulnérables ne le sont pas parce qu’ils utilisent une technologie ancienne, mais parce qu’ils reposent sur des habitudes de développement dépassées. On retrouve souvent :
- des requêtes SQL écrites à la main avec concaténation directe ;
- l’utilisation excessive de variables issues de
$_GET,$_POSTou$_REQUESTsans validation sérieuse ; - des correctifs partiels basés uniquement sur l’échappement de caractères ;
- des comptes de base de données trop permissifs ;
- un manque de tests de sécurité avant la mise en ligne.
Les développeurs débutants cherchent parfois à “nettoyer” les entrées utilisateur avec quelques fonctions rapides. Or, en sécurité applicative, un filtrage approximatif ne suffit pas. La protection doit être structurelle. C’est précisément le rôle des requêtes préparées.
La méthode la plus fiable : utiliser des requêtes préparées
La meilleure protection contre les injections SQL en PHP consiste à utiliser des requêtes préparées, aussi appelées prepared statements. Le principe est simple : la structure SQL est envoyée séparément des valeurs. Ainsi, les données utilisateur ne sont jamais interprétées comme du code SQL.
Avec PDO, cela donne par exemple :
$pdo = new PDO('mysql:host=localhost;dbname=site;charset=utf8mb4', 'user', 'password');
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND password = :password");
$stmt->execute([
'email' => $_POST['email'],
'password' => $_POST['password']
]);
$user = $stmt->fetch();
Avec MySQLi, on peut aussi écrire :
$stmt = $conn->prepare("SELECT * FROM users WHERE email = ? AND password = ?");
$stmt->bind_param("ss", $_POST['email'], $_POST['password']);
$stmt->execute();
$result = $stmt->get_result();
Dans les deux cas, l’idée est la même : on ne concatène plus les valeurs directement dans la requête. C’est la base de toute protection sérieuse contre l’injection SQL.
Pourquoi les requêtes préparées sont si efficaces
Les requêtes préparées séparent clairement deux éléments :
- la commande SQL ;
- les paramètres transmis par l’application.
Cette séparation empêche un attaquant de fermer une chaîne, d’ajouter une condition, un commentaire SQL ou une commande supplémentaire. Même si l’entrée contient des caractères spéciaux, elle est traitée comme une donnée, pas comme une instruction.
Pour un site PHP moderne, c’est une règle non négociable. Toute requête contenant une valeur dynamique doit être préparée.
Ne pas se contenter de l’échappement des caractères
On voit encore beaucoup de code qui utilise addslashes(), htmlspecialchars() ou même mysqli_real_escape_string() comme protection principale. C’est insuffisant, et parfois totalement hors sujet.
htmlspecialchars() sert à sécuriser l’affichage HTML, pas les requêtes SQL. Cette fonction est utile contre les failles XSS, mais elle ne protège pas une base de données. addslashes(), de son côté, est trop générique et ne doit pas être utilisé pour sécuriser une requête. Quant à mysqli_real_escape_string(), même s’il peut réduire certains risques dans des cas précis, il ne remplace pas les requêtes préparées.
En clair, si votre sécurité repose sur l’échappement manuel, votre site reste exposé. La bonne approche consiste à revoir la logique de construction des requêtes.
Valider et typer toutes les données utilisateur
Les requêtes préparées sont essentielles, mais elles ne dispensent pas d’une validation sérieuse des entrées. Une donnée peut être sûre du point de vue SQL et pourtant invalide pour le métier de l’application. Il faut donc contrôler chaque valeur selon ce qu’elle est censée contenir.
Quelques exemples simples :
- un identifiant doit être un entier ;
- une adresse e-mail doit respecter un format valide ;
- une date doit suivre un format attendu ;
- une valeur de tri doit appartenir à une liste fermée ;
- un statut ne doit accepter que certaines options prévues.
Exemple en PHP :
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
exit('Identifiant invalide');
}
Cette étape est importante, notamment pour les paramètres transmis dans l’URL. À ce sujet, adopter une structure d’URL propre améliore la cohérence technique du site, mais il faut aussi vérifier chaque paramètre reçu côté serveur, même si l’URL semble bien construite.
Faire attention aux parties de requête qui ne peuvent pas être paramétrées directement
Les requêtes préparées protègent les valeurs, mais pas tous les éléments SQL. Certains morceaux d’une requête, comme le nom d’une colonne, le sens du tri ou le nom d’une table, ne peuvent pas être passés comme paramètres de la même manière. C’est là qu’apparaissent encore beaucoup d’erreurs.
Exemple risqué :
$orderBy = $_GET['sort'];
$sql = "SELECT * FROM products ORDER BY $orderBy";
Ici, même si le reste de la requête est correct, le paramètre de tri peut être manipulé. La bonne solution consiste à utiliser une liste blanche :
$allowedSorts = ['name', 'price', 'created_at'];
$orderBy = $_GET['sort'] ?? 'created_at';
if (!in_array($orderBy, $allowedSorts, true)) {
$orderBy = 'created_at';
}
$sql = "SELECT * FROM products ORDER BY $orderBy";
On n’accepte donc que des valeurs connues à l’avance. Ce principe de whitelist est indispensable pour tout élément SQL non paramétrable.
Sécuriser les formulaires de connexion et les espaces d’administration
Les pages de connexion sont des cibles fréquentes. Si elles sont mal codées, elles peuvent permettre un contournement d’authentification. Mais la sécurité ne s’arrête pas à la requête SQL. Il faut aussi appliquer plusieurs bonnes pratiques :
- ne jamais stocker les mots de passe en clair ;
- utiliser
password_hash()pour l’enregistrement ; - utiliser
password_verify()lors de la connexion ; - limiter le nombre de tentatives ;
- journaliser les connexions suspectes ;
- protéger l’administration par des droits d’accès stricts.
Exemple correct :
$stmt = $pdo->prepare("SELECT id, password_hash FROM users WHERE email = :email");
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch();
if ($user && password_verify($_POST['password'], $user['password_hash'])) {
// Connexion réussie
} else {
// Échec
}
Cette méthode évite à la fois l’injection SQL et les mauvaises pratiques de stockage des mots de passe.
Limiter les privilèges du compte de base de données
Même avec un code propre, il est prudent de réduire les conséquences d’une éventuelle faille. Pour cela, il faut appliquer le principe du moindre privilège au compte utilisé par le site pour se connecter à la base de données.
Concrètement, le compte SQL de l’application ne devrait pas avoir plus de droits que nécessaire. Si le site a seulement besoin de lire et d’écrire dans certaines tables, inutile de lui donner des permissions d’administration globale, de suppression sur toutes les tables ou de création de nouveaux utilisateurs.
Par exemple, évitez d’utiliser le compte root dans une application web. Créez un utilisateur dédié avec des droits limités sur une base précise. Ainsi, si une faille existe malgré tout, l’impact sera réduit.
Masquer les erreurs SQL en production
Les messages d’erreur détaillés sont très utiles en développement, mais ils peuvent aider un attaquant en production. Une erreur SQL affichée à l’écran peut révéler le nom des tables, la structure des colonnes, le type de base utilisée ou la requête exécutée.
Sur un site en ligne, il faut :
- désactiver l’affichage public des erreurs ;
- enregistrer les erreurs dans des logs sécurisés ;
- afficher un message générique à l’utilisateur.
Exemple :
ini_set('display_errors', 0);
ini_set('log_errors', 1);
error_reporting(E_ALL);
Cette configuration ne remplace pas la prévention, mais elle évite de donner des informations techniques inutiles à un utilisateur malveillant.
Mettre à jour PHP, les dépendances et le CMS si nécessaire
Un site PHP ne se résume pas à son code métier. Il repose souvent sur un framework, des bibliothèques tierces, un CMS, des extensions ou des modules maison. Une faille peut apparaître dans l’un de ces composants et rouvrir la porte à des attaques indirectes.
Il faut donc maintenir à jour :
- la version de PHP ;
- le serveur web ;
- le moteur de base de données ;
- les dépendances installées via Composer ;
- les plugins et thèmes si le projet repose sur un CMS.
Les environnements obsolètes cumulent souvent plusieurs faiblesses : sécurité réduite, incompatibilités, mauvaises performances et dette technique. Une maintenance régulière est indispensable pour garder un site fiable.
Tester son site pour détecter les injections SQL
On ne peut pas affirmer qu’un site est protégé sans le vérifier. Les tests permettent d’identifier les zones sensibles avant qu’un attaquant ne les trouve. Ils peuvent être réalisés manuellement, automatiquement, ou idéalement avec les deux approches.
Tests manuels de base
Sur les champs de formulaire et les paramètres d’URL, on peut vérifier le comportement de l’application avec des entrées inhabituelles :
- apostrophes ;
- guillemets ;
- valeurs très longues ;
- formats inattendus ;
- paramètres manquants ou modifiés.
Si le site affiche des erreurs SQL ou réagit de manière incohérente, il faut auditer la zone concernée.
Outils d’audit et revue de code
Les outils spécialisés peuvent aider à repérer des points faibles, mais ils ne remplacent pas une revue de code. L’idéal est de combiner :
- analyse statique du code ;
- tests d’intrusion ;
- audit des requêtes SQL ;
- contrôle des permissions SQL ;
- vérification des logs.
Une revue attentive permet souvent de détecter des concaténations oubliées dans des scripts anciens, des exports CSV, des modules de recherche ou des outils d’administration internes.
Les erreurs fréquentes à éviter absolument
Sur les projets PHP, certaines erreurs reviennent souvent. Les connaître permet de les corriger plus vite :
- construire une requête avec
".$_GET['id']."ou".$_POST['email']."; - penser que filtrer les apostrophes suffit ;
- utiliser le même compte SQL avec tous les droits ;
- faire confiance aux données cachées d’un formulaire ;
- négliger les paramètres de tri, de pagination ou de recherche ;
- laisser des scripts d’administration accessibles ;
- conserver du code ancien non revu.
Il faut aussi se méfier des choix techniques faits au tout début d’un projet. Par exemple, lors de la réflexion sur le choisir son nom de domaine, on pense souvent au SEO et à la marque, mais la sécurité applicative doit être prise en compte avec le même sérieux dès la phase de conception.
Exemple de démarche simple pour sécuriser un site PHP existant
Si votre site est déjà en ligne et que vous souhaitez réduire rapidement le risque d’injection SQL, voici une méthode pragmatique :
- recenser toutes les requêtes SQL du projet ;
- identifier celles qui utilisent une concaténation de variables ;
- remplacer ces requêtes par des prepared statements ;
- valider chaque entrée selon son type réel ;
- mettre en place des listes blanches pour les tris et filtres ;
- réduire les droits du compte SQL ;
- désactiver l’affichage des erreurs en production ;
- tester les formulaires, URL et zones d’administration ;
- documenter les bonnes pratiques pour les futures évolutions.
Cette démarche est particulièrement utile sur les sites développés progressivement, avec plusieurs intervenants, ou sur des applications qui ont évolué sans refonte globale. Dans ce type de contexte, les failles ne sont pas toujours visibles immédiatement.
PDO ou MySQLi : lequel choisir pour mieux se protéger ?
Les deux extensions permettent d’utiliser des requêtes préparées. PDO est souvent préféré pour sa souplesse, sa syntaxe homogène et sa compatibilité avec plusieurs moteurs de base de données. MySQLi reste une bonne option si le projet repose exclusivement sur MySQL.
Le point essentiel n’est pas tant l’outil que la manière de l’utiliser. Un projet en PDO peut être vulnérable si les requêtes sont mal construites, tout comme un projet MySQLi peut être sûr s’il applique correctement les prepared statements, la validation des entrées et la gestion des droits.
Pour un nouveau projet, PDO est souvent un choix pertinent, notamment pour garder un code propre, maintenable et plus simple à faire évoluer.
La sécurité SQL doit faire partie du développement courant
Éviter les injections SQL ne doit pas être vu comme une tâche ponctuelle à traiter après coup. C’est une règle de développement à intégrer dans toutes les fonctionnalités : formulaire de contact, espace client, recherche interne, panier e-commerce, tableau de bord, API, import de données ou module de réservation.
Dès qu’une donnée externe entre dans l’application, il faut se poser les bonnes questions :
- d’où vient cette donnée ;
- quel type de valeur est attendu ;
- comment est-elle validée ;
- entre-t-elle dans une requête SQL ;
- la requête est-elle préparée ;
- les permissions SQL sont-elles adaptées ?
Ce réflexe permet de réduire fortement les risques, tout en améliorant la qualité globale du code.
Sur un site PHP, la prévention des injections SQL repose avant tout sur une discipline technique claire : ne jamais concaténer des données utilisateur dans une requête, utiliser systématiquement des requêtes préparées, valider les entrées, limiter les droits SQL et tester régulièrement l’application. Ces mesures sont simples en apparence, mais elles font une différence majeure entre un site fragile et un site robuste.
Pour une entreprise, la sécurité n’est pas un détail technique réservé aux développeurs. Elle conditionne la fiabilité du site, la protection des données et la confiance des utilisateurs. Mieux vaut investir dans un code propre dès le départ que corriger une faille après un incident. Sur le long terme, c’est aussi ce qui rend un site plus stable, plus maintenable et plus performant.



