flawopen.com/SQL Injection/PHP
Imagine a form that only ever expects a ticket number, like 482. SQL injection is what happens when someone types something sneaky into that box instead of a number — a trick phrase that makes the system say "show me every ticket" instead of just ticket 482, because the system never checked that what it received was actually just a number.
SQL injection happens when user-controlled input — most often $_GET or $_POST straight from the request — gets inserted directly into a database query's text. PHP's long history of loosely-typed, easy string concatenation and a huge body of outdated tutorials makes this one of the most persistently copy-pasted vulnerable patterns in the language.
In 2015, UK telecom TalkTalk suffered a breach affecting over 150,000 customers after attackers exploited a SQL injection flaw in a legacy web page inherited through a company acquisition. The UK's data protection regulator fined TalkTalk £400,000, describing the failure as preventable and basic.
Source: UK Information Commissioner's Office enforcement notice, 2016 — see References below.// $_GET value straight into the query
$query = "SELECT * FROM users
WHERE id = " . $_GET['id'];
$result = $mysqli->query($query);
// value is bound, never concatenated
$stmt = $pdo->prepare(
"SELECT * FROM users WHERE id = :id"
);
$stmt->execute(['id' => $_GET['id']]);
$result = $stmt->fetch();
PDO compiles the query with the :id placeholder first, then binds the value separately when execute() runs — the value is never merged into the query text the database parses, so it can't change the query's structure regardless of what characters it contains.
Escaping functions quote special characters for the current connection's character set, but historically had bypass issues under certain multi-byte encodings, and they only protect the specific spot they're applied to — one missed call anywhere in the codebase reopens the hole. Prepared statements remove the class of bug entirely rather than mitigating it case by case.
PDO supports named (:id) and positional (?) placeholders; mysqli's prepared-statement API only supports positional ?. Mixing the two conventions by copying from the wrong documentation silently fails.
The original mysql_query() API (removed in PHP 7) had no parameter binding mechanism at all. Code following an old blog post or Stack Overflow answer using these functions is a strong signal the whole query layer needs rewriting, not patching.
addslashes() is not connection-charset-aware and is not a database escaping function — it's unsafe for this purpose even as a mitigation, let alone as a substitute for parameterization.
A large share of PHP tutorials predate PDO's widespread adoption. Popularity and age of a pattern say nothing about its safety.
A dropdown constrains the browser's UI, not the actual HTTP request — an attacker can send any value directly, bypassing the form entirely.
grep -rn '\->query(' --include="*.php" . | grep '\$_\(GET\|POST\|REQUEST\)'
grep -rn "mysql_query(" --include="*.php" .
grep -rn "addslashes(" --include="*.php" .
Both are safe when used with prepared statements and bound parameters — the safety comes from parameterization, not from which library you pick.
Laravel's query builder and Eloquent ORM parameterize by default. Raw query methods (DB::raw()) built with string concatenation bypass that protection the same way as in any other framework.
Only for identifiers that can't be bound as parameters (table or column names), and even then it needs a strict allow-list, not general-purpose escaping — never for values, which should always be bound parameters.