为什么不能将表名传递给准备好的PDO语句?

$stmt = $dbh->prepare('SELECT * FROM :table WHERE 1');
if ($stmt->execute(array(':table' => 'users'))) {
    var_dump($stmt->fetchAll());
}

是否有另一种安全的方法将表名插入SQL查询?有了安全,我的意思是我不想做

$sql = "SELECT * FROM $table WHERE 1"

当前回答

使用前者本质上并不比后者更安全,您需要清除输入,无论它是参数数组的一部分还是简单变量的一部分。因此,我不认为对$table使用后一种形式有任何错误,只要您在使用它之前确保$table的内容是安全的(字母加下划线?)。

其他回答

(回答晚了,请参考我的旁注)。

同样的规则也适用于创建“数据库”。

不能使用预准备语句绑定数据库。

例如:

CREATE DATABASE IF NOT EXISTS :database

不会起作用。使用安全列表代替。

旁注:我添加了这个答案(作为一个社区wiki),因为它经常用于结束问题,有些人在尝试绑定数据库而不是表和/或列时发布了类似的问题。

使用前者本质上并不比后者更安全,您需要清除输入,无论它是参数数组的一部分还是简单变量的一部分。因此,我不认为对$table使用后一种形式有任何错误,只要您在使用它之前确保$table的内容是安全的(字母加下划线?)。

在PDO中,表名和列名不能被参数替换。

在这种情况下,您只需要手动过滤和清除数据。一种方法是将简写参数传递给动态执行查询的函数,然后使用switch()语句创建用于表名或列名的有效值白列表。这样用户输入就不会直接进入查询。例如:

function buildQuery( $get_var ) 
{
    switch($get_var)
    {
        case 1:
            $tbl = 'users';
            break;
    }

    $sql = "SELECT * FROM $tbl";
}

通过不保留默认大小写或使用返回错误消息的默认大小写,可以确保只使用您希望使用的值。

要理解为什么绑定表(或列)名不起作用,必须理解预处理语句中的占位符是如何工作的:它们不是简单地作为(适当转义的)字符串替换,并执行结果SQL。相反,一个被要求“准备”语句的DBMS会提出一个完整的查询计划,包括它将如何执行该查询,包括它将使用哪些表和索引,无论您如何填充占位符,它都是一样的。

SELECT name FROM my_table WHERE id =:value的计划将与您替换为:value的计划相同,但是看起来相似的SELECT名称FROM:table WHERE id =:value不能计划,因为DBMS不知道您实际上要从哪个表中选择。

这也不是像PDO这样的抽象库可以或应该解决的问题,因为它会破坏预处理语句的两个关键目的:1)允许数据库提前决定查询将如何运行,并多次使用相同的计划;2)通过将查询逻辑与变量输入分离来防止安全问题。

我想知道你是否可以提供你自己的自定义消毒功能,就像这样简单:

$value = preg_replace('/[^a-zA-Z_]*/', '', $value);

我还没有真正想过,但似乎除了字符和下划线之外,删除任何东西都可以。