I understand that using prepared statement prevents injection as the prepared statement execution consists of two stages: prepare and execute.
OK, but I do not really get what is going on if a bound parameter value is then used as a User-Defined Variables in MySQL.
Is the initially safe bound parameters process can be used for execution (and therefore injection) in STEP 2 ?
// The user input that may be the target for injection
$userInput = "input";
// STEP 1 -------------------
$q = "SET @param1 = :param1;";
// Execute query to set mysql user-defined variables
$param = [
'param1' => $userInput
];
$stmt = $pdo->prepare($q);
$stmt->execute($param);
// STEP 2 -------------------
// Query DB with User-Defined Variables
$q = "
SELECT ...
WHERE
table.field1 = @param1 OR
table.field2 = @param1 OR
table.field3 = @param1
";
// Query
$stmt = $pdo->query($q);
// STEP 3 -------------------
// Fetch Data
$row = $stmt->fetch();
I use this to avoid multiple similar named parameters like in the following example hereafter as
you cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on
from manual. It's a mess to maintain for complex queries:
$q = "
SELECT ...
WHERE
table.field1 = :param1_1 OR
table.field2 = :param1_2 OR
table.field3 = :param1_2
";
$param = [
'param1_1' => $userInput
'param1_2' => $userInput
'param1_3' => $userInput
];
$stmt = $pdo->prepare($q);
$stmt->execute($param);
Yes, you can assume that a user variable takes the place of a single scalar value in a query, just like a bound parameter placeholder. It's an effective protection against SQL injection.
Proof: Try to perform an SQL injection using a user variable.
SET @s = 'Robert''; DROP TABLE Students;--';
SELECT * FROM Students WHERE name = @s;
This does NOT drop the table. It probably returns nothing, because there is no student with that strange, long name (unless you go to school with Little Bobby Tables).
However, I wonder if a query like this:
SELECT ...
WHERE
table.field1 = @param1 OR
table.field2 = @param1 OR
table.field3 = @param1
Indicates that field1, field2, and field3 should really be a single field in a child table. If you're searching for the same value in multiple columns, it could be a repeating group. For example, if it's phone1, phone2, phone3, that's a multi-valued attribute that should be stored in one column over multiple rows in a child table. Then you can search with a single parameter.