Injection SQL
L’injection SQL est une faille de sécurité critique qui permet à un attaquant d’exécuter des requêtes SQL malveillantes en exploitant des entrées utilisateur non sécurisées (ex : formulaires, URL, cookies). En PHP, cela arrive souvent quand des données utilisateur sont concatenées directement dans une requête SQL sans validation ni échappement.
Exemple vulnérable :
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
Un attaquant pourrait injecter ' OR '1'='1 comme nom d’utilisateur pour contourner l’authentification.
Risques :
- Vol/modification de données (ex : suppression de tables avec
DROP TABLE users). - Prise de contrôle du serveur (si le SGBD a des privilèges élevés).
- Fuites d’informations sensibles (ex : mots de passe, données clients).
Prompt pour l'IA
« Analyse ce code PHP pour détecter des vulnérabilités d’injection SQL. Vérifie les points suivants :
- Concatenation directe : Les variables utilisateur (
$_GET,$_POST,$_COOKIE, etc.) sont-elles insérées dans des requêtes SQL sans échappement ? - Fonctions dangereuses : Utilisation de
mysqli_query(),mysql_query()(obsolète), ouPDOen mode non préparé ? - Échappement insuffisant : Les fonctions comme
mysqli_real_escape_string()sont-elles utilisées seules (sans requêtes préparées) ? - Requêtes dynamiques : Y a-t-il des requêtes construites avec des opérateurs comme
OR,UNION, ou des commentaires SQL (--) dans les entrées utilisateur ?
Recommandations attendues :
- Remplacer par des requêtes préparées (PDO ou MySQLi avec
prepare()). - Valider les entrées (ex : filtrage avec
filter_var()). - Utiliser des ORM (ex : Doctrine, Eloquent) pour abstraire les requêtes.
- Désactiver l’affichage des erreurs SQL en production (
display_errors = Off). »
Explication détaillée
A. Mécanisme de l’injection SQL
L’injection SQL exploite le fait que le code PHP et les données utilisateur sont mélangés dans la requête. Le SGBD (MySQL, PostgreSQL, etc.) ne fait pas la différence entre le code SQL et les données, ce qui permet d’altérer la logique de la requête.
Exemple d’attaque classique :
-
Formulaire de connexion :
-- Requête attendue : SELECT * FROM users WHERE username = 'admin' AND password = '123456' -- Injection via le champ "username" : ' OR '1'='1' --La requête devient :
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '...'Résultat : Tous les utilisateurs sont retournés (le
--commente la suite).
B. Techniques de prévention
1. Requêtes préparées (Prepared Statements)
Solution la plus sûre : Séparer la structure de la requête des données.
- Avec PDO (recommandé) :
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password'); $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username"); $stmt->execute(['username' => $_POST['username']]); $user = $stmt->fetch(); - Avec MySQLi :
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?"); $stmt->bind_param("s", $_POST['username']); $stmt->execute();
2. Échappement des entrées (moins sûr seul)
mysqli_real_escape_string(): Échappe les caractères spéciaux (',",\), mais ne protège pas contre toutes les injections (ex : attaques via des encodages alternatifs).$username = mysqli_real_escape_string($conn, $_POST['username']);Limite : Inutile si vous utilisez des requêtes préparées.
3. Validation des entrées
- Filtrage strict : Rejeter les entrées qui ne correspondent pas à un format attendu.
if (!preg_match('/^[a-zA-Z0-9_]+$/', $_POST['username'])) { die("Nom d'utilisateur invalide"); }
4. Bonnes pratiques complémentaires
- Désactiver les erreurs SQL en production :
mysqli_report(MYSQLI_REPORT_OFF); // Désactive les warnings - Utiliser un ORM (ex : Doctrine) pour éviter d’écrire du SQL brut.
- Principes de moindre privilège : Le compte SQL utilisé par PHP ne doit pas avoir de droits
DROP,ALTER, etc.
C. Exemples concrets d’injections et corrections
Cas 1 : Injection via un paramètre GET (liste d’articles)
Code vulnérable :
$article_id = $_GET['id'];
$query = "SELECT * FROM articles WHERE id = $article_id";
Attaque :
http://site.com/article.php?id=1; DROP TABLE articles--
→ Supprime la table.
Correction (PDO) :
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id = :id");
$stmt->execute(['id' => (int)$_GET['id']]); // Cast en entier pour plus de sécurité
Cas 2 : Injection via un formulaire d’inscription
Code vulnérable :
$email = $_POST['email'];
$query = "INSERT INTO users (email) VALUES ('$email')";
Attaque :
'), ('hacker@example.com'), ('-- → Insère plusieurs emails.
Correction (MySQLi) :
$stmt = $conn->prepare("INSERT INTO users (email) VALUES (?)");
$stmt->bind_param("s", $_POST['email']);
$stmt->execute();
Cas 3 : Injection via un ORDER BY dynamique
Code vulnérable :
$order = $_GET['order']; // Ex : "username ASC"
$query = "SELECT * FROM users ORDER BY $order";
Attaque :
username ASC; DELETE FROM users--
Correction :
- Liste blanche des colonnes autorisées :
$allowed_columns = ['username', 'email', 'created_at']; $order = in_array($_GET['order'], $allowed_columns) ? $_GET['order'] : 'username'; $stmt = $pdo->prepare("SELECT * FROM users ORDER BY $order ASC");
D. Outils pour tester les vulnérabilités
- SQLMap : Outil automatisé pour détecter et exploiter les injections.
sqlmap -u "http://site.com/login.php" --data="username=test&password=test" --risk=3 --dbs - PHPStan ou Psalm : Détectent les concatenations SQL dangereuses en analyse statique.
- OWASP ZAP : Scanner de sécurité pour tester les formulaires web.
E. Pourquoi les requêtes préparées sont-elles sûres ?
- Séparation des données et du code : Les paramètres sont envoyés après la compilation de la requête, empêchant toute modification de la structure SQL.
- Typage strict : Les données sont associées à un type (ex :
string,int), ce qui bloque les injections basées sur des chaînes.
Exemple interne :
-- Requête préparée compilée (avant l'exécution) :
SELECT * FROM users WHERE username = ?
-- L'attaquant injecte ' OR '1'='1, mais le SGBD traite cela comme une VALUE, pas du CODE :
SELECT * FROM users WHERE username = '' OR '1'='1'
-- → Recherche littérale de la chaîne, pas une condition logique.
Conclusion
L’injection SQL reste l’une des vulnérabilités les plus exploitées (classée #1 dans l’OWASP Top 10). En PHP, les requêtes préparées (PDO/MySQLi) sont la seule solution fiable. Les méthodes comme mysqli_real_escape_string() ou les filtres manuels sont insuffisantes seules. Une approche défensive (validation + requêtes préparées + moindre privilège) est essentielle pour sécuriser une application.
Ressources utiles :