Search code examples
phpmysqlpdoprepared-statementsql-injection

Is it safe to use Bound Parameters as User-Defined Variable in MySQL?


THE ISSUE

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();

WHY DO I WANT TO USE THIS APPROACH ?

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);

Solution

  • 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.