安全地使用准备好的语句查询数据库
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 数据对象的精神前身)有一个->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 SETname
= ':name' LIMIT 1 会返回 true 但“名称”不会更新。更多信息:***.com/questions/2124294/…以上是关于安全地使用准备好的语句查询数据库的主要内容,如果未能解决你的问题,请参考以下文章
为啥使用 mysql 准备好的语句比使用常见的转义函数更安全?
带有 sql 转义的动态 mysql 查询是不是与准备好的语句一样安全?