安全地使用准备好的语句查询数据库

Posted

技术标签:

【中文标题】安全地使用准备好的语句查询数据库【英文标题】: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: " 。 $选择->查询字符串;您很快就会明白为什么您的查询不起作用:)。正如其他人所提到的,没有专门用于标识符转义的功能,只需要有创意并自己创建一些东西。 【参考方案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。该函数非常简单,您应该能够编写自己的函数并相当容易地检查它是否存在错误。

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

    对于每个部分:

      将所有` 替换为``。 在开头和结尾添加`,除非该部分为空。*

    .加入零件。

例如,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 但“名称”不会更新。更多信息:***.com/questions/2124294/…

以上是关于安全地使用准备好的语句查询数据库的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 mysql 准备好的语句比使用常见的转义函数更安全?

从准备好的语句中获取数据

带有 sql 转义的动态 mysql 查询是不是与准备好的语句一样安全?

什么时候在 PHP Mysqli 中使用准备好的语句? - 用户表单搜索输入与选择查询

Spring JPA - 准备好的语句查询?

带有变量的Mysqli准备好的语句列[重复]