2008-10-08 33 views
191

为什么我无法将表名称传递给准备好的PDO语句?PHP 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" 

回答

168

请参阅以下内容: http://us3.php.net/manual/en/book.pdo.php#69304

表和列名不能在PDO参数来代替。

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

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

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

通过不留默认情况下,或者使用返回一条错误消息默认情况下,可以确保只有您要使用的值习惯。

+12

+1用于白名单选项而不是使用任何种类的动态方法。另一种替代方法可能是将可接受的表名映射到一个数组,其中键对应于潜在的用户输入(例如`array('u'=>'users','t'=>'table','n'=>'nonsensitive_data ')`等等) – Kzqai 2011-12-22 18:05:20

+2

读过这个,对我来说,这里的例子为坏的输入生成无效的SQL,因为它没有`default`。如果使用这种模式,你应该将你的`case`s标记为`default`,或者添加一个明确的错误情况,比如`default:throw new InvalidArgumentException;` – IMSoP 2015-10-22 09:34:59

+2

我在想一个简单的`if(in_array($ tbl ,''users','products',...]){$ sql =“SELECT * FROM $ tbl”;}`。Thanks for the idea。 – philtune 2016-03-02 17:20:06

4

使用前者并不比后者本质上更安全,你需要净化输入无论是参数数组或一个简单的变量的一部分。所以如果在使用$table之前确保$table的内容是安全的(alphanum加下划线?),我认为使用$table的后一种形式没有任何问题。

+0

考虑到第一个选项不起作用,您必须使用某种形式的动态查询构建。 – 2008-10-08 11:58:16

+0

是的,问题提到它不会工作。我试图描述为什么即使试图这样做也不是非常重要。 – 2008-10-08 12:01:02

119

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

SELECT name FROM my_table WHERE id = :value的计划可不管你代替:value相同,但表面上类似SELECT name FROM :table WHERE id = :value不能计划的,因为DBMS不知道什么表格你究竟要从中选择。

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

11

我看到这是一个老帖子,但我发现它有用,我想我会分享类似于@kzqai提出了一个解决方案:

我有收到像两个参数的函数...

function getTableInfo($inTableName, $inColumnName) { 
    .... 
} 

里面我对证阵列我设置,以确保只有表和列“福地”表是访问:

$allowed_tables_array = array('tblTheTable'); 
$allowed_columns_array['tblTheTable'] = array('the_col_to_check'); 

然后PHP检查运行PDO罗前像...一样我的

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

部分想知道,您可以提供自己的自定义功能,消毒像这样简单:

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

我还没有真正通过它认为,但似乎除了字符消除任何与下划线可能会起作用。

0

至于在这个线程的主要问题,其他职位讲明为什么准备语句时,我们不能绑定值的列名,所以这里是一个解决方案:

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

相关问题