Introduction : la question qui hante tout développeur PHP
\n\nSi vous avez déjà écrit des requêtes SQL en PHP, vous avez forcément entendu ce mantra : "Utilise PDO et les prepared statements, et tu seras en sécurité". Mais est-ce vraiment le cas ? Sur Stack Overflow, la question "Are PDO prepared statements sufficient to prevent SQL injection?" a cumulé des milliers de vues et des dizaines de débats animés. Et pour cause.
\n\nDans cet article, nous ne nous contenterons pas d'une simple réponse "oui" ou "non". Nous allons plonger sous le capot de PDO, comprendre exactement comment fonctionnent les requêtes préparées, et découvrir dans quels cas même PDO peut vous trahir. Prêt ? C'est parti.
\n\nQu'est-ce qu'une injection SQL et pourquoi est-elle dangereuse ?
\n\nAvant de parler de protection, rappelons à quoi ressemble l'ennemi. L'injection SQL est une attaque où un pirate "injecte" du code SQL malveillant via des données d'entrée. Le classique :
\n\n// Mauvais code : ne faites jamais ça\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n\n\nSi un pirate envoie admin' OR '1'='1, la requête devient :
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n\n\nEt voilà, accès aux données sans mot de passe. Et si on envoie '; DROP TABLE users; -- ? Adieu, table des utilisateurs.
PDO et les requêtes préparées : comment ça marche ?
\n\nPDO (PHP Data Objects) propose un mécanisme de prepared statements (requêtes préparées). L'idée est simple : vous séparez la structure de la requête des données. Au lieu d'insérer des variables directement dans la chaîne, vous utilisez des placeholders :
\n\n// Code sécurisé avec PDO\n$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");\n$stmt->execute([':username' => $_POST['username']]);\n\n\nMais que se passe-t-il sous le capot ? C'est le point clé que beaucoup négligent.
\n\nMode émulé vs. mode natif des prepared statements
\n\nPDO peut fonctionner selon deux modes :
\n\n- \n
- Mode émulé (par défaut dans les anciennes versions de PHP) — PDO échappe lui-même les données et les insère dans la requête côté PHP. C'est toujours sécurisé, mais théoriquement vulnérable si l'échappement échoue (par exemple avec des encodages non standards). \n
- Mode natif — PDO envoie deux messages au serveur de base de données : d'abord le modèle de requête, puis les données séparément. Le serveur traite les données comme des valeurs, pas comme du code. C'est absolument sécurisé. \n
Pour activer le mode natif, désactivez l'émulation :
\n\n$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);\n\n\nMaintenant, la requête est envoyée au serveur en deux étapes. Même si un pirate envoie ' OR '1'='1, le serveur MySQL ou PostgreSQL interprète cela comme une simple chaîne de caractères, et non comme du code SQL. Magique !
Mais est-ce vraiment parfait ? Les pièges de PDO
\n\nRevenons à la question de Stack Overflow. La réponse est "oui, les prepared statements de PDO suffisent" — mais avec des réserves. Voici les situations où la protection peut faillir.
\n\n1. Identifiants dynamiques (noms de tables, colonnes)
\n\nLes prepared statements ne fonctionnent que pour les valeurs (données). Si vous insérez un nom de table ou de colonne, les placeholders ne vous aideront pas :
\n\n// Dangereux ! Le nom de table ne peut pas être passé via un placeholder\n$stmt = $pdo->prepare("SELECT * FROM :table WHERE id = :id");\n$stmt->execute([':table' => $_GET['table'], ':id' => 1]); // Erreur !\n\n\nPDO insérera simplement le nom de la table comme une chaîne, et la requête échouera. Si vous utilisez l'émulation, un pirate pourrait envoyer users; DROP TABLE users; --. Solution : une liste blanche des valeurs autorisées.
2. Requêtes LIKE et caractères génériques
\n\nSi vous utilisez