为什么不能将表名传递给准备好的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"
为什么不能将表名传递给准备好的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"
当前回答
(回答晚了,请参考我的旁注)。
同样的规则也适用于创建“数据库”。
不能使用预准备语句绑定数据库。
例如:
CREATE DATABASE IF NOT EXISTS :database
不会起作用。使用安全列表代替。
旁注:我添加了这个答案(作为一个社区wiki),因为它经常用于结束问题,有些人在尝试绑定数据库而不是表和/或列时发布了类似的问题。
其他回答
要理解为什么绑定表(或列)名不起作用,必须理解预处理语句中的占位符是如何工作的:它们不是简单地作为(适当转义的)字符串替换,并执行结果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);
我还没有真正想过,但似乎除了字符和下划线之外,删除任何东西都可以。
(回答晚了,请参考我的旁注)。
同样的规则也适用于创建“数据库”。
不能使用预准备语句绑定数据库。
例如:
CREATE DATABASE IF NOT EXISTS :database
不会起作用。使用安全列表代替。
旁注:我添加了这个答案(作为一个社区wiki),因为它经常用于结束问题,有些人在尝试绑定数据库而不是表和/或列时发布了类似的问题。
简短的回答是否定的,你不能在PDO的Prepared execute语句中使用动态表名、字段名等,因为它会向它们添加引号,这会中断查询。但是如果你可以净化它们,那么你就可以安全地把它们放在查询本身中,就像你使用MySQLi一样。
正确的方法是使用mysqli的mysqli_real_escape_string()函数,因为mysql_real_escape_string被匆忙地从PHP中删除,而没有考虑它如何影响动态结构应用程序。
$unsanitized_table_name = "users' OR '1'='1"; //SQL Injection attempt
$sanitized_table_name = sanitize_input($unsanitized_table_name);
$stmt = $dbh->prepare("SELECT * FROM {$unsanitized_table_name} WHERE 1"); //<--- REALLY bad idea
$stmt = $dbh->prepare("SELECT * FROM {$sanitized_table_name} WHERE 1"); //<--- Not ideal but hey, at least you're safe.
//PDO Cant sanitize everything so we limp along with mysqli instead
function sanitize_input($string)
{
$mysqli = new mysqli("localhost","UsahName","Passerrrd");
$string = $mysqli->real_escape_string($string);
return $string;
}
我看到这是一个旧帖子,但我发现它很有用,我想分享一个类似于@kzqai建议的解决方案:
我有一个函数,它接收两个参数,比如…
function getTableInfo($inTableName, $inColumnName) {
....
}
在里面,我检查我所设置的数组,以确保只有具有“blessed”表的表和列是可访问的:
$allowed_tables_array = array('tblTheTable');
$allowed_columns_array['tblTheTable'] = array('the_col_to_check');
然后运行PDO之前的PHP检查看起来像…
if(in_array($inTableName, $allowed_tables_array) && in_array($inColumnName,$allowed_columns_array[$inTableName]))
{
$sql = "SELECT $inColumnName AS columnInfo
FROM $inTableName";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
}