似乎关于这个主题的每个问题都是mysqli预处理语句和PHP中的直接mysqli查询之间的区别,但是当准备好的语句不能满足您的需求时,该怎么做。
准备好的陈述当然是执行简单查询时的方法:
$stmt = $connection->prepare("SELECT * FROM my_table WHERE id = ?");
但是当事情变得更复杂的时候呢?从PHP手册:
但是,标识符(例如表名或列名),SELECT列表中不允许使用[markers]来命名SELECT语句返回的列,或者指定二元运算符的两个操作数,例如=等号。
这会成为复杂查询的问题,做需要指定二元运算符的两个操作数,或mysqli_prepare
具有的其他一些限制。
在我的情况下,我需要执行一些查询来返回博客条目的结果(这是一个简化的示例,我的连接变量实际上是博客类的私有属性,但你明白了):
$query = $connection->query("SELECT * FROM my_table WHERE $field = '$search'");
在此示例中,$field
变量是要搜索的列,$search
变量是要搜索的内容。使用预准备语句无法进行此类查询。
我已经为这些功能做了很多精心规划,因为我知道只有X个列要搜索,我使用条件来检查$field
是否等于其中一列,mysqli_real_escape_string
以逃避任何可能的引号字符。但这是好习惯吗?基于我在SO上阅读和回答的内容,您应该始终使用预准备语句,但我从未在这些示例中看到过复杂的查询。有没有更好的方法来防止SQL注入,使用预准备语句的更高级方法,或者我应该在这里坚持非常仔细的验证?
答案 0 :(得分:3)
是和否:有必要再次检查$field
变量白名单 - 这是防止sql注入的唯一方法 - 但在mysqli_real_escape_string
上使用$field
没有意义{1}}变量。如果您的列名是保留字或以例如数字开头,则应在反引号中引用,但就是这样。
您仍然应该为$search
变量使用预准备语句,尽管此处mysqli_real_escape_string
也会这样做(而不是准备好的语句,而不是两者)。
答案 1 :(得分:2)
关于:
$query = $connection->query("SELECT * FROM my_table WHERE $field = '$search'");
使用预准备语句无法进行此类查询。
可以使用预准备语句实现此目的。只要输入不是来自客户端,在执行查询时对字符串使用串联是(有些)安全的,例如:
'SELECT * FROM `my_table` WHERE `'.$field.'` = ?'
请注意,我已为我准备好的语句添加了?
标记,只要$field
是有效的列名,它就不会中断我的查询。
但是,这样做是不好的做法,如果您想在不同的地方执行查询,则应该实施switch
或if … 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.'` = ?';
在这种情况下,我使用了一个数字索引,你可以使用一个键o任何更好的服务。
但如果输入来自用户,则总是使用预准备语句。它或者手动 scape你的字符串。