1

Here's the problematic PHP function:

//Get data associated with $criteria from db
function getUserData($criteria, $value) {
    //obtain user data from db based on $criteria=$value
    global $pdo;
    //echo $criteria . " " . $value;
    try {
        $sql = 'SELECT id, first, last, email, userid FROM users WHERE :criteria= :value';
        //var_dump($sql);
        $st = $pdo->prepare($sql);
        $st->bindValue(':criteria', $criteria);
        $st->bindValue(':value', $value);
        $st->execute();
    }
    catch (PDOException $ex) {
        $error = "Failed to obtain user data.";
        $errorDetails = $ex->getMessage();
        include 'error.html.php';
        exit();
    }
    $row = $st->fetch();
    //var_dump($row);
        if ($row)
        {
            $userdata = array();
            $userdata['id'] = $row['id'];
            $userdata['first'] = $row['first'];
            $userdata['last'] = $row['last'];
            $userdata['email'] = $row['email'];
            $userdata['userid'] = $row['userid'];
            return $userdata;
        }
        return FALSE;
}

I use this function to return a whole row of data associated with specific column in it.

When used at it's current state, with a call like that getUserData("email", "John_Stewart_2013"), it returns false, meaning an empty result, while the same query runs fine in MySQL CLI.

If I, on the other hand, substitute the query string $sql with :

$sql = "SELECT id, first, last, email, userid FROM users WHERE $criteria='$value'";

And comment out the bindValue calls, Every thing runs fine in PHP, and the query returns as desired.

But the problem is, those function arguments are user-submitted form data, meaning the solution is vulnerable to SQL Injection.

What's wrong here in the first query form?

MadeOfAir
  • 2,933
  • 5
  • 31
  • 39
  • [Same Q&A here](http://stackoverflow.com/questions/8314043/how-to-dynamically-build-queries-with-pdo) – Peter Jan 27 '13 at 19:27

1 Answers1

2

You can't use bindValue with column names I'm afraid.

If you think about what a prepared statement is, this should become more obvious. Basically, when you prepare a statement with the database server, it creates an execution plan for the query beforehand, rather than generating it at the time of running the query. This makes it not only faster but more secure, as it knows where it's going, and the datatypes that it will be using and which are going to be input.

If the column/table names were bindable in any way, it would not be able to generate this execution plan, making the whole prepared statement idea somewhat redundant.

The best way would be to use a hybrid query like so:

$sql = "SELECT id, first, last, email, userid FROM users WHERE $criteria = :value";

I'm going to hope that the $criteria column isn't entirely free form from the client anyway. If it is, you'd be best limiting it to a specific set of allowed options. A simplistic way to do would be to build an array of allowed columns, and check if it's valid with in_array, like so:

$allowed_columns = array('email', 'telephone', 'somethingelse');
if (!in_array($criteria, $allowed_columns))
{
    $error = "The column name passed was not allowed.";
    $errorDetails = $ex->getMessage();
    include 'error.html.php';
    exit;
}
Rudi Visser
  • 21,350
  • 5
  • 71
  • 97
  • Thanks @Rudi Visser.I'll go with the first one.Although it is not obvious why binding column names doesn't work. – MadeOfAir Jan 27 '13 at 20:12
  • @MadeOfAir Because the idea of a prepared statement is to define the execution plan before actual execution with the values. If the column names were to be dynamic, it couldn't create this execution plan. – Rudi Visser Jan 27 '13 at 21:12
  • I guess it makes query execution more exciting at least!.LOL – MadeOfAir Jan 27 '13 at 23:32