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