PHP: PDO prepared statements not preventing all injection?
The problem
I use PDO prepared statements everywhere but a security review flagged a query where the table name is dynamic. How do I safely handle dynamic identifiers?
The solution
Short answer
Prepared statement placeholders (? or :name) only work for values (data), not for identifiers like table or column names. PDO has no built-in way to bind an identifier — that's why your dynamic table name query isn't actually protected, even though you're "using prepared statements."
What to do instead
- Whitelist validation — the safest and most common approach. Only allow table names that exist in a known, hardcoded list:
$allowedTables = ['users', 'orders', 'invoices'];
if (!in_array($tableName, $allowedTables, true)) {
throw new InvalidArgumentException('Invalid table name');
}
$sql = "SELECT * FROM `$tableName` WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $id]);
-
If you truly can't whitelist, at minimum strictly validate the format (e.g. only allow
[A-Za-z0-9_]+matching an existing schema table) and quote the identifier properly for your DB engine (backticks for MySQL, double quotes for Postgres). This is weaker than a whitelist and still risky if validation is incomplete — whitelist is strongly preferred. -
Never concatenate user input directly into the identifier position and never try to "escape" it the same way you'd escape a value — identifiers aren't values, so
PDO::quote()is not designed for this and shouldn't be relied on for table/column names.
Bottom line
Values → use prepared statement placeholders. Identifiers (table/column names) → validate against a whitelist before building the SQL string.
Last updated a day ago
Comments (0)
No comments yet — be the first to weigh in.
Log in to leave a comment. Log in