Une requête construite en collant des chaînes mêle vos instructions aux données de quelqu'un d'autre. La base ne peut pas distinguer les unes des autres : une valeur choisie avec soin cesse alors d'être une valeur et devient une partie de la question.
// wrong, and it is wrong even if you escape
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";
Le remède n'est pas l'échappement
L'échappement tente de rendre inoffensive une donnée dangereuse. Les requêtes préparées font ARRIVER la donnée SÉPARÉMENT, de sorte qu'elle ne puisse jamais être lue comme une instruction. On dit d'abord la question à la base, et les valeurs ensuite.
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
Ce qu'un paramètre ne peut pas être
Un paramètre tient lieu de VALEUR. Il ne peut pas tenir lieu de nom de table, de nom de colonne ni de sens de tri - cela fait partie de la question. Si ces éléments doivent venir d'une saisie, vérifiez-les contre une liste que vous avez écrite.
$allowed = ['created_at', 'total', 'status'];
$sort = in_array($in, $allowed, true) ? $in : 'created_at';
$sql = "SELECT * FROM orders ORDER BY $sort DESC";
Trouver celles que vous avez
grep -rn "SELECT.*\$_\(GET\|POST\|REQUEST\)" --include="*.php" . | head
Toute requête contenant une superglobale à l'intérieur de la chaîne est le motif. Il y en a d'ordinaire une poignée, et d'ordinaire elles sont anciennes.
Et la seconde ligne de défense
Donnez à l'utilisateur de l'application uniquement ce dont il a besoin : quatre verbes sur une seule base. Une injection réussie via un compte qui ne peut pas supprimer une table fait bien moins de dégâts qu'une injection via un compte qui le peut.