Can I use a PDO prepared statement to bind an identifier (a table or field name) or a syntax keyword?

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

Leave a Comment