14

I'm working on a dynamic query that uses variables to specify a table, a field/column, and a value to search for. I've gotten the query to work as expected without the variables, both in phpMyAdmin (manually typing the query) and from within the code by concatenating the variables into a complete query.

However, when I use bindParam() or bindValue() to bind the variables, it returns an empty array.

Here's my code:

function search_db($db, $searchTerm, $searchBy, $searchTable){
    try{
        $stmt = $db->prepare('
            SELECT 
                * 
            FROM 
                ?
            WHERE 
                ? LIKE ?
        ');
        $stmt->bindParam(1, $searchTable);
        $stmt->bindParam(2, $searchBy);
        $stmt->bindValue(3, '%'. $searchTerm.'%');
        $stmt->execute();
    } catch(Exception $e) {
        return array();
    }
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// database initialization, creates the $db variable
require(ROOT_PATH . "include/database.php");
$matches = search_db($db, 'search term', 'myColumn', 'myTable');

var_dump($matches);

Expected results: an array of rows from the database

Actual results: an empty array

Chris Baker
  • 49,926
  • 12
  • 96
  • 115
Andrew
  • 21
  • 1
  • 7

1 Answers1

27

Unfortunately, placeholder can represent a data literal only. So, a very common pitfall is a query like this:

$opt = "id";
$sql = "SELECT :option FROM t";
$stm  = $pdo->prepare($sql);
$stm->execute([':option' => $opt]);
$data = $stm->fetchAll();

This statement will return just a literal string 'id' in the fieldset, not the value of the column named id.

So, a developer must take care of identifiers oneself - PDO offers no help for this matter.

To make a dynamical identifier safe, one has to follow 2 strict rules:

  • to format identifiers properly
  • to verify it against a hardcoded whitelist.

To format an identifier, one has to apply these 2 rules:

  • Enclose the identifier in backticks.
  • Escape backticks inside by doubling them.

After such formatting, it is safe to insert the $table variable into query. So, the code would be:

$field = "`".str_replace("`","``",$field)."`";
$sql   = "SELECT * FROM t ORDER BY $field";

However, although such formatting would be enough for the cases like ORDER BY, for most other cases there is a possibility for a different sort of injection: letting a user choose a table or a field they can see, we may reveal some sensitive information, like a password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:

$allowed = array("name","price","qty");
$key     = array_search($_GET['field'], $allowed);
$field   = $allowed[$key];
$query   = "SELECT $field FROM t"; //value is safe

For keywords, the rules are same, but of course there is no formatting available - thus, only whitelisting is possible and ought to be used:

$dir = $_GET['dir'] == 'DESC' ? 'DESC' : 'ASC'; 
$sql = "SELECT * FROM t ORDER BY field $dir"; //value is safe
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345