Introduction : pourquoi les injections SQL sont-elles toujours d'actualité ?
\n\nImaginez : votre site fonctionne parfaitement, les utilisateurs s'inscrivent, laissent des commentaires, passent des commandes. Et soudain, votre base de données tombe entre les mains d'un attaquant. Non pas parce que les hackers sont des génies, mais parce que quelqu'un a oublié de mettre un point d'interrogation dans une requête SQL.
\n\nLes injections SQL ne sont pas une menace archaïque des années 2000. En 2024, l'OWASP les classe toujours parmi les trois vulnérabilités les plus dangereuses des applications web. Et la question "How can I prevent SQL injection in PHP?" sur Stack Overflow a dépassé 1,5 million de vues — et continue d'augmenter. Preuve que le problème est bien vivant.
Dans cet article, nous allons voir comment verrouiller définitivement la porte aux injections SQL en PHP en 2025. Sans magie, sans frameworks complexes, uniquement du code propre et des bonnes pratiques.
\n\nQu'est-ce qu'une injection SQL en termes simples ?
\n\nUne injection SQL se produit lorsque la saisie utilisateur devient une partie de la commande SQL, et non de simples données. Exemple concret :
\n\n<?php\n// MAUVAIS CODE — ne faites jamais cela !\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n?>\n\nSi un attaquant saisit dans le champ username la chaîne ' OR '1'='1, la requête devient :
SELECT * FROM users WHERE username = '' OR '1'='1'\n\nCela renvoie tous les utilisateurs. Et si l'on saisit '; DROP TABLE users; --, toute la table peut être supprimée. Flippant, non ?
Bonnes pratiques pour se protéger des injections SQL en PHP en 2025
\n\n1. Utilisez PDO avec des requêtes préparées (c'est la norme)
\n\nPDO (PHP Data Objects) est une interface universelle pour travailler avec les bases de données. Son principal avantage : il sépare la logique SQL des données. Vous écrivez une requête avec des placeholders, et PDO se charge de l'échappement sécurisé.
\n\n<?php\n// BON CODE — utilisez PDO\n$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';\n$pdo = new PDO($dsn, $user, $pass, [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n PDO::ATTR_EMULATE_PREPARES => false, // désactive l'émulation\n]);\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');\n$stmt->execute(['email' => $_POST['email']]);\n$user = $stmt->fetch();\n?>\n\nPourquoi ça marche ? La base de données reçoit la structure de la requête séparément des données. Même si l'utilisateur saisit du SQL malveillant, il sera interprété comme une simple chaîne de caractères, et non comme une partie de la commande.
\n\n2. Mysqli avec des requêtes préparées (alternative)
\n\nSi vous travaillez uniquement avec MySQL, vous pouvez utiliser mysqli — mais uniquement avec des requêtes préparées, et non avec l'échappement via mysqli_real_escape_string().
<?php\n$mysqli = new mysqli('localhost', 'user', 'pass', 'testdb');\n$stmt = $mysqli->prepare('INSERT INTO comments (user_id, text) VALUES (?, ?)');\n$stmt->bind_param('is', $userId, $commentText);\n$stmt->execute();\n?>\n\nImportant : en 2025, mysqli_real_escape_string() existe toujours, mais compter dessus pour la sécurité est une mauvaise idée. Il échappe uniquement les chaînes, mais ne protège pas contre les injections dans les champs numériques ou les opérateurs LIKE.
3. Filtrage et validation — deuxième ligne de défense
\n\nLes requêtes préparées sont la base. Mais ajoutez-y un filtrage par type de données. Si un nombre est attendu, vérifiez que c'est bien un nombre. Si un email est attendu, vérifiez le format.
\n\n<?php\n$id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);\nif ($id === false) {\n throw new InvalidArgumentException('L\\'ID doit être un entier');\n}\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');\n$stmt->execute(['id' => $id]);\n?>\n\nCombiner