Una query costruita incollando stringhe mescola le sue istruzioni con i dati di qualcun altro. Il database non riesce a distinguere gli uni dagli altri: così un valore scelto con cura smette di essere un valore e diventa parte della domanda.
// wrong, and it is wrong even if you escape
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";
Il rimedio non è l'escaping
L'escaping cerca di rendere innocuo un dato pericoloso. I prepared statement fanno ARRIVARE il dato SEPARATAMENTE, così non può mai essere letto come istruzione. Al database si dice prima la domanda e poi i valori.
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
Che cosa un segnaposto non può essere
Un segnaposto sta al posto di un VALORE. Non può stare al posto di un nome di tabella, di un nome di colonna o di un verso di ordinamento: quelli sono parte della domanda. Se devono arrivare dall'input, li confronti con un elenco scritto da lei.
$allowed = ['created_at', 'total', 'status'];
$sort = in_array($in, $allowed, true) ? $in : 'created_at';
$sql = "SELECT * FROM orders ORDER BY $sort DESC";
Trovi quelle che ha
grep -rn "SELECT.*\$_\(GET\|POST\|REQUEST\)" --include="*.php" . | head
Qualsiasi query con una superglobale dentro la stringa è il pattern. Di solito ce n'è una manciata e di solito sono vecchie.
E la seconda linea
Dia all'utente dell'applicazione soltanto ciò che gli serve: quattro verbi su un solo database. Un'injection riuscita attraverso un account che non può cancellare una tabella fa assai meno danno di una attraverso un account che può.