【问题标题】:MySQLi prepared statements vs queriesMySQLi 准备好的语句与查询
【发布时间】:2014-06-21 00:44:16
【问题描述】:

似乎关于这个主题的每个问题都是 mysqli 准备好的语句和 PHP 中的直接 mysqli 查询之间的区别,但是当准备好的语句不能满足您的需求时该怎么办。

在执行简单查询时,准备好的语句肯定是要走的路:

$stmt = $connection->prepare("SELECT * FROM my_table WHERE id = ?");

但是当事情变得更复杂时呢?来自 PHP 手册:

但是,在为 SELECT 语句返回的列命名的选择列表中,或指定二元运算符的两个操作数(例如= 等号。

这成为复杂查询的问题,确实需要指定二元运算符的两个操作数,或mysqli_prepare 具有的其他一些限制。

在我的例子中,我需要执行一些查询来返回博客条目的结果(这是一个简化的例子,我的连接变量实际上是博客类的私有属性,但你明白了):

$query = $connection->query("SELECT * FROM my_table WHERE $field = '$search'");

在此示例中,$field 变量是要搜索的列,$search 变量是要搜索的内容。准备好的语句无法进行此类查询。

我已经为这些函数做了很多仔细的规划,因为我知道只有 X 列可供搜索,所以我使用条件来检查 $field 是否等于这些列之一, 和 mysqli_real_escape_string 转义任何可能的引号字符。但这是好的做法吗?根据我在 SO 上阅读和回答的内容,您应该始终使用准备好的语句,但我从未在这些示例中看到过复杂的查询。有没有更好的方法来防止 SQL 注入,更高级的方法来使用准备好的语句,还是我应该在这里坚持非常仔细的验证?

【问题讨论】:

  • 新来者选择mysqli 而不是PDO 通常是由于研究有限,并且通过名称相似性假设它是更容易的迁移路径。 (不是。)

标签: php security mysqli prepared-statement


【解决方案1】:

是与否:有必要再次检查$field 变量是否列入白名单——这是防止sql 注入的唯一方法——但在$field 变量上使用mysqli_real_escape_string 毫无意义。如果您的列名是保留字或例如以数字开头,则应该用反引号将其引用,仅此而已。

您仍应为$search 变量使用准备好的语句,尽管这里mysqli_real_escape_string 也可以(而不是准备好的语句,而不是两者)。

【讨论】:

  • 酷,我怎么能只为$search 变量使用准备好的语句?我是否必须将查询放入字符串变量中,然后将该字符串传递给prepare 方法?
  • @samrap 是的,您直接注入 $field 变量,但您将 $search 变量替换为问号并准备该字符串。
  • ...或一列包含空格 ;-)
【解决方案2】:

关于:

$query = $connection->query("SELECT * FROM my_table WHERE $field = '$search'");

这种类型的查询不能使用准备好的语句。

可以使用准备好的语句来实现这一点。只要输入不是来自客户端,在执行查询时对字符串使用连接(在某种程度上)是安全的,例如:

'SELECT * FROM `my_table` WHERE `'.$field.'` = ?'

请注意,我在准备好的语句中包含了? 标记,只要$field 是有效的列名,它就不会破坏我的查询。

但是,这样做是一种不好的做法,如果您想对不同的列或表名执行查询,则应该实现 switchif … else 块。示例:

switch($columnId) {
    case 0:
        $q = 'SELECT * FROM `my_table` WHERE `column0` = ?';
        break;
    case 1:
        $q = 'SELECT * FROM `my_table` WHERE `column1` = ?';
    …
}

更新

或者按照 hakre 的建议,在列名中使用白名单:

$columns = ['column0', 'column1', 'column2'];
$columnId = $_GET['column'];

if ( isset($columns[$columnId]) ) $field = $columnId;
else throw new Exception('Column not defined');

$q = 'SELECT * FROM `my_table` WHERE `'.$columns[$columnId].'` = ?';

或者

if ( isset($columns[$columnId]) ) $field = $columns[$columnId];
else throw new Exception('Column not defined');

$q = 'SELECT * FROM `my_table` WHERE `'.$field.'` = ?';

在这种情况下,我使用了数字索引,您可以使用任何更适合您的键。

但如果输入来自用户,您应该始终使用准备好的语句。要么是这样,要么是手动转义你的字符串。

【讨论】:

  • 我喜欢 switch 的想法,但我宁愿坚持@jeroen 提供的更清洁的解决方案
  • 方法相同(参见第一个示例),但带有示例代码;我只是添加了另一种方法来实现相同的结果(第二个示例)。
  • 作为开关的替代方案,您还可以在此处使用数组创建列名的白名单。如果使用了不在该数组中的列名,则抛出异常。
  • 白名单的好处是使用相同的而不是 id :) 因为数字比名称更难记住。
猜你喜欢
  • 1970-01-01
  • 2012-12-01
  • 2016-09-11
  • 1970-01-01
  • 2018-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多