安全地使用预准备语句来查询数据库

时间:2010-02-04 22:05:26

标签: php mysql pdo sql-injection prepared-statement

我正在尝试在允许的查询中编写 多才多艺 的函数,但 安全注入 即可。下面的代码按原样抛出错误,但是如果我使用'name'而不是':field'运行它,它可以正常工作。

$field = "name";
$value = "joe";

function selectquery($field, $value)
  {
  global $dbcon;

  $select = $dbcon->prepare('SELECT * FROM tester1 WHERE :field = :value');
  if($select->execute(array(':field' => $field, ':value' => $value)));
    {
    $row = $select->fetch();
    for ($i=0; $i<3; $i++)
      {
      echo $row[$i]."\n";
      }
    }  
  }

如何在不允许注入攻击的情况下允许更改表/字段/值? mysql_real_escape_string()似乎有点像向后退一步。有什么想法吗?

6 个答案:

答案 0 :(得分:3)

我可能会弄错,但我不相信你可以在PDO中提供字段作为参数。

为什么不将它指定为函数的参数?与用户提供的数据不同,字段是有限的,定义良好且不经常更改。如在

selectquery('name',$value);

并让您的查询

$field = "name";
$value = "joe";

function selectquery($field, $value)
  {
  global $dbcon;

  $select=$dbcon->prepare("SELECT * FROM tester1 WHERE $field = :value");
  if($select->execute(array(':value' => $value)));
 //etcetera  

由于您自己为函数调用提供字段名称,因此除非您担心自己会使用SQL注入攻击自己,否则这是安全的。

如果由于某些奇怪的原因,该字段的名称来自用户输入,您可以创建一个允许字段数组。这样,您就可以安全地进行注射,因为这些值只能来自您的数组。我不知道为什么字段名称来自用户输入,因此不受信任,除非您正在制作API?其他方面,可能有更好的方法来实现目标。

无论如何,这可能是一个潜在的解决方案,使用白名单来表名:

$field = "name";
$value = "joe";

$allowed_fields=array('name','other_name','sandwich');

function selectquery($field_name, $value)
  {
  global $dbcon,$allowed_fields;

  if(!in_array($field_name,$allowed_fields)){ return false; }
  else{ $field=$field_name; }

  $select=$dbcon->prepare("SELECT * FROM tester1 WHERE $field = :value");
  if($select->execute(array(':value' => $value)));
  //etcetera

答案 1 :(得分:1)

使用MDB2 autoExecute
http://pear.php.net/manual/en/package.database.mdb2.intro-auto.php

<?php
// Once you have a valid MDB2 object named $mdb2...
$table_name = 'user';

$fields_values = array(
    'id'      => 1,
    'name'    => 'Fabien',
    'country' => 'France'
);
$types = array('integer', 'text', 'text');

$mdb2->loadModule('Extended');
$affectedRows = $mdb2->extended->autoExecute($table_name, $fields_values,
                        MDB2_AUTOQUERY_INSERT, null, $types);

if (PEAR::isError($affectedRows)) {
    die($affectedRows->getMessage());
}
?>

答案 2 :(得分:0)

数据库标识符(列名,表名和数据库名)不能也不应该被转义,因此您不能在SQL准备的查询中使用它们。

有时候你可能需要反推这些标识符(对MySQL使用`,对SQLite使用)。

答案 3 :(得分:0)

绑定变量将其绑定为数据,特别是阻止更改查询的语法。此外,具有固定语法允许引擎分析准备好的查询一次,然后针对每组值快速运行它们。我建议你不要在SQL之上构建一个手持层,但如果必须,可以考虑一个preg_replace('/ \ W /','',$ field)。

答案 4 :(得分:0)

遗憾的是,PHP数据对象没有公开引用字段标识符的方法。

作为替代方案,PEAR::MDB2(PHP数据对象的精神前身)有->quoteIdentifier()选项,可让您以安全的方式实现所需。

function selectquery($field, $value)
  {
  global $dbcon;

  $select = $dbcon->prepare('SELECT * FROM tester1 WHERE ' . $dbcon->quoteIdentifier($field) . ' = :value');
  if($select->execute(array('field' => $field, 'value' => $value)));
    {
    $row = $select->fetchRow();
    for ($i=0; $i<3; $i++)
      {
      echo $row[$i]."\n";
      }
    }  
  }

我知道这个解决方案不是最优的(在开发项目的过程中更改抽象层很麻烦)但不幸的是,PDO没有提供做你想做的事情的安全方法。

答案 5 :(得分:0)

借调Andrew Moore的回答:唯一的方法是标识符引用,PDO不提供必要的方法。您可能只想借用其标识符引用的实现,而不是使用MDB2。该功能非常简单,您应该能够自己编写并轻松地查找错误。

  1. .上的输入字符串拆分为部分列表(可能只有一个)

  2. 每个部分:

    1. 将所有`替换为``
    2. 在开头和结尾添加`,除非该部分为空。 *
  3. 使用.加入部件。

  4. 例如,quote_identifier("one two.three")应为`one two`.`three` - 非常简单。

    为了更加安全,您还可以验证字符串是否包含任何非法的字符,即使在带引号的标识符中也是如此(特别是空值,请参阅the MySQL docs)但实际上MySQL应该捕获这些字符。 MDB2没有打扰。

    * :此检查是必要的,因为.columnname是合法的,应引用.`columnname`而非``.`columnname`