代码之家  ›  专栏  ›  技术社区  ›  Jrgns

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

  •  218
  • Jrgns  · 技术社区  · 16 年前

    为什么不能将表名传递给准备好的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"
    
    7 回复  |  直到 6 年前
        1
  •  194
  •   Your Common Sense    6 年前

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

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

    function buildQuery( $get_var ) 
    {
        switch($get_var)
        {
            case 1:
                $tbl = 'users';
                break;
        }
    
        $sql = "SELECT * FROM $tbl";
    }
    

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

        2
  •  130
  •   IMSoP    11 年前

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

    计划 SELECT name FROM my_table WHERE id = :value 无论你用什么替代都一样 :value 但是看起来很相似 SELECT name FROM :table WHERE id = :value 无法计划,因为DBMS不知道您实际上要从哪个表中选择。

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

        3
  •  12
  •   Don    11 年前

    我看到这是一篇老文章,但我发现它很有用,我想我会分享一个类似于@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);
    }
    
        4
  •  4
  •   Adam Bellaire    16 年前

    使用前者本质上并不比后者更安全,您需要清理输入,不管它是参数数组的一部分还是简单变量。所以我不认为使用后一个表单有什么问题 $table ,前提是确保 美元桌 是否安全(字母加下划线?)使用前。

        5
  •  2
  •   community wiki Funk Forty Niner    6 年前

    (迟些回答,查阅我的旁注)。

    当试图创建“数据库”时,同样的规则也适用。

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

    即。:

    CREATE DATABASE IF NOT EXISTS :database
    

    不会起作用。改用安全列表。

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

        6
  •  0
  •   Phil LaNasa    10 年前

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

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

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

        7
  •  0
  •   man    10 年前

    对于这个线程中的主要问题,其他的文章明确说明了为什么在准备语句时不能将值绑定到列名,所以这里有一个解决方案:

    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%的安全性,但它允许在列名称作为动态字符串“进入”时对它们进行一些控制,并且可以在用户端进行更改。此外,不需要用表列名和类型构建一些数组,因为它们是从信息_模式中提取的。