Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/290.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP PDO语句可以接受表或列名作为参数吗?_Php_Pdo - Fatal编程技术网

PHP PDO语句可以接受表或列名作为参数吗?

PHP PDO语句可以接受表或列名作为参数吗?,php,pdo,Php,Pdo,为什么不能将表名传递给准备好的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"

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

PDO中的参数不能替换表名和列名

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

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

    $sql = "SELECT * FROM $tbl";
}
通过不保留默认大小写或使用返回错误消息的默认大小写,您可以确保只使用希望使用的值。

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

从my_表中选择名称(其中id=:value)的计划将与替换:value的内容相同,但无法计划看似相似的从:表中选择名称(其中id=:value),因为DBMS不知道实际要从哪个表中选择


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

我看到这是一篇老文章,但我发现它很有用,我想分享一个类似于@kzqai建议的解决方案:

我有一个函数,它接收两个参数,比如

function getTableInfo($inTableName, $inColumnName) {
    ....
}
在内部,我检查已设置的数组,以确保只有具有祝福表的表和列才可访问:

$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);
}

我有一部分想知道,您是否可以提供您自己的自定义消毒功能,简单如下:

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

我还没有仔细考虑过,但似乎除了字符和下划线之外,删除任何东西都可能有效。

至于这个线程中的主要问题,其他帖子清楚地说明了为什么我们在准备语句时不能将值绑定到列名,所以这里有一个解决方案:

class myPdo{
    private $user   = 'dbuser';
    private $pass   = 'dbpass';
    private $host   = 'dbhost';
    private $db = 'dbname';
    private $pdo;
    private $dbInfo;
    public function __construct($type){
        $this->pdo = new PDO('mysql:host='.$this->host.';dbname='.$this->db.';charset=utf8',$this->user,$this->pass);
        if(isset($type)){
            //when class is called upon, it stores column names and column types from the table of you choice in $this->dbInfo;
            $stmt = "select distinct column_name,column_type from information_schema.columns where table_name='sometable';";
            $stmt = $this->pdo->prepare($stmt);//not really necessary since this stmt doesn't contain any dynamic values;
            $stmt->execute();
            $this->dbInfo = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
    }
    public function pdo_param($col){
        $param_type = PDO::PARAM_STR;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] == $col){
                if(strstr($arr['column_type'],'int')){
                    $param_type = PDO::PARAM_INT;
                    break;
                }
            }
        }//for testing purposes i only used INT and VARCHAR column types. Adjust to your needs...
        return $param_type;
    }
    public function columnIsAllowed($col){
        $colisAllowed = false;
        foreach($this->dbInfo as $k => $arr){
            if($arr['column_name'] === $col){
                $colisAllowed = true;
                break;
            }
        }
        return $colisAllowed;
    }
    public function q($data){
        //$data is received by post as a JSON object and looks like this
        //{"data":{"column_a":"value","column_b":"value","column_c":"value"},"get":"column_x"}
        $data = json_decode($data,TRUE);
        $continue = true;
        foreach($data['data'] as $column_name => $value){
            if(!$this->columnIsAllowed($column_name)){
                 $continue = false;
                 //means that someone possibly messed with the post and tried to get data from a column that does not exist in the current table, or the column name is a sql injection string and so on...
                 break;
             }
        }
        //since $data['get'] is also a column, check if its allowed as well
        if(isset($data['get']) && !$this->columnIsAllowed($data['get'])){
             $continue = false;
        }
        if(!$continue){
            exit('possible injection attempt');
        }
        //continue with the rest of the func, as you normally would
        $stmt = "SELECT DISTINCT ".$data['get']." from sometable WHERE ";
        foreach($data['data'] as $k => $v){
            $stmt .= $k.' LIKE :'.$k.'_val AND ';
        }
        $stmt = substr($stmt,0,-5)." order by ".$data['get'];
        //$stmt should look like this
        //SELECT DISTINCT column_x from sometable WHERE column_a LIKE :column_a_val AND column_b LIKE :column_b_val AND column_c LIKE :column_c_val order by column_x
        $stmt = $this->pdo->prepare($stmt);
        //obviously now i have to bindValue()
        foreach($data['data'] as $k => $v){
            $stmt->bindValue(':'.$k.'_val','%'.$v.'%',$this->pdo_param($k));
            //setting PDO::PARAM... type based on column_type from $this->dbInfo
        }
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);//or whatever
    }
}
$pdo = new myPdo('anything');//anything so that isset() evaluates to TRUE.
var_dump($pdo->q($some_json_object_as_described_above));
以上只是一个例子,所以不用说,复制->粘贴将不起作用。根据你的需要进行调整。 现在,这可能无法提供100%的安全性,但它允许在列名作为动态字符串输入并且可能在用户端更改时对其进行一些控制。此外,不需要使用表列名和类型构建一些数组,因为它们是从信息模式中提取的。

稍后回答,请参阅我的旁注

在尝试创建数据库时,同样的规则也适用

不能使用预处理语句绑定数据库

即:

这是行不通的。使用安全列表代替


旁注:我将此答案添加为社区wiki,因为它通常用于关闭问题,有些人在试图绑定数据库而不是表和/或列时发布了类似的问题。

考虑到第一个选项不起作用,您必须使用某种形式的动态查询构建。是的,这个问题提到它不起作用。我试图描述为什么这样做并不十分重要。+1表示白名单选项,而不是使用任何类型的动态方法。另一种选择可能是将可接受的表名映射到一个数组,该数组具有与潜在用户输入相对应的键,例如数组'u'=>'users','t'=>'table','n'=>'nonsensitive_data'等。综上所述,我发现这里的示例为错误输入生成无效SQL,因为它没有默认值。如果使用此模式,您应该将其中一个案例标记为默认案例,或者添加一个明确的错误案例,例如default:throw new InvalidArgumentException;我在想一个简单的if-in_数组$tbl,['users','products',…]{$sql=SELECT*FROM$tbl;}。谢谢你的主意。我想念mysql\u real\u escape\u string。也许在这里我可以说,没有人插嘴说,但PDO不需要它。另一个问题是,动态表名破坏了SQL inspec
是的,但没有考虑到PDO的prepare语句模拟,它可能会参数化SQL对象标识符,尽管我仍然同意它可能不应该参数化。@eggyal我猜模拟的目的是使标准功能在所有DBMS风格上都能工作,而不是添加全新的功能。标识符的占位符还需要任何DBMS都不直接支持的独特语法。PDO是一个相当低级的包装器,例如,它不提供TOP/LIMIT/OFFSET子句的SQL生成,因此作为一个特性,这有点不合适。MySQL表名可以包含其他字符。请看@PhilLaNasa,事实上有些人认为他们应该需要的参考资料。由于大多数DBMS都不区分大小写,将名称存储在非区分字符中,例如:MyLongTableName很容易正确读取,但如果检查存储的名称,则可能是MyLongTableName,它的可读性不强,因此,我的表名实际上更具可读性。有一个很好的理由不将其作为函数:您应该很少根据任意输入选择表名。您几乎肯定不希望恶意用户将用户或预订替换为Select*From$table。白名单或严格的模式匹配,例如名称开头的报告uuu后面只跟1到3位数字,在这里确实很重要。对于简短的解决方案很好,但为什么不只是$pdo->query$Sql呢?在准备必须绑定变量的查询时,这主要是出于习惯。另外,读取重复调用速度更快w/此处执行示例中没有重复调用
CREATE DATABASE IF NOT EXISTS :database