【问题标题】:Safely using prepared statements to query database安全地使用准备好的语句查询数据库
【发布时间】:2011-01-13 07:11:47
【问题描述】:

我正在尝试编写一个多功能在允许进行的查询中,而且不会被注入的函数em>。下面的代码会按原样引发错误,但如果我使用 '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() 似乎有点倒退。有什么想法吗?

【问题讨论】:

  • 我建议做一个 echo "SQL statement: " 。 $选择->查询字符串;您很快就会明白为什么您的查询不起作用:)。正如其他人所提到的,没有专门用于标识符转义的功能,只需要有创意并自己创建一些东西。

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


【解决方案1】:

我可能弄错了,但我不相信您可以在 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

【讨论】:

  • 觉得我刚刚爱上了你的允许字段数组。输入确实来自用户输入。这将是安全的,因为用户输入从未真正接触过查询,只是针对它进行评估,对吧?
  • 是的,据我所知是安全的。如果 $fields 参数中有一些不寻常的输入,那么它根本不会在数组中找到并且函数返回。如果用户可以发送一个整数(也许输入来自一个选择菜单),一个更简洁的选择可能是使用这个整数作为数组键(即 $field=$allowed_fields[$field_name]; if($field= =''){return false;})。这样 $field 将更直接地从数组中设置。
【解决方案2】:

使用 MDB2 自动执行
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());
}
?>

【讨论】:

    【解决方案3】:

    接着 Andrew Moore 的回应:唯一的方法是引用标识符,而 PDO 没有提供必要的方法。您可能只想借用它的标识符引用实现,而不是使用 MDB2。该函数非常简单,您应该能够编写自己的函数并相当容易地检查它是否存在错误。

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

    2. 对于每个部分:

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

    例如,quote_identifier("one two.three") 应该是 `one two`.`three`——非常简单。

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

    *:这个检查是必要的,因为.columnname 是合法的,应该引用.`columnname` 而不是``.`columnname`

    【讨论】:

      【解决方案4】:

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

      作为替代方案,PEAR::MDB2(PHP 数据对象的精神前身)有一个-&gt;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】:

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

        【讨论】:

          【解决方案6】:

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

          有时您可能需要对这些标识符进行反引号(对 MySQL 使用 `,对 SQLite 使用 ")。

          【讨论】:

          • 第一次听说标识符无法转义。我过去做过很多次。所有具有明确分隔符和格式规则的内容都可以转义。
          • 我最近遇到了一个关于 PDO 的问题,并且在我的字段和值周围使用带有刻度的 UPDATE。我花了很长时间才弄清楚为什么 UPDATE users SET name = ':name' LIMIT 1 会返回 true 但“名称”不会更新。更多信息:stackoverflow.com/questions/2124294/…
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-02
          • 2010-11-21
          • 2012-08-12
          相关资源
          最近更新 更多