PHP:使用准备好的语句并防止 SQL 注入与逃逸
Posted
技术标签:
【中文标题】PHP:使用准备好的语句并防止 SQL 注入与逃逸【英文标题】:PHP: using prepared statements and protecting against SQL injection vs escape 【发布时间】:2012-07-12 09:05:08 【问题描述】:我明白,准备好的语句是寻求 SQL 注入保护的最终方法。但是,它们以有限的方式提供覆盖;例如,如果我让用户决定按操作的顺序(即,它是 ASC 还是 DESC?等),则准备好的语句没有覆盖。
我知道我可以将用户输入映射到一个预定义的白名单。但是,这只有在可以预先创建或彻底猜测白名单时才有可能。
例如,在我上面提到的情况下(ASC 或 DESC),这可以很容易地根据接受值列表进行映射和验证。但是不存在无法对照白名单验证部分 SQL 语句的情况吗?
如果存在这种情况,那么推荐的方法是什么?
如果我要使用底层数据库的内置转义实用程序(例如 mysql 的 mysql_real_escape_string)来转义 user_input,我会在哪里失败?
我问这个问题的假设是我总是用引用的值构造我的 sql 语句——即使是整数......
让我们看看下面的例子并反思一下..
select $fields from $table where Age='$age' order by $orderby_pref
假设所有变量都是用户提供的。
如果我要 mysql_real_escape_string 上面 SQL 中的所有变量(而不是使用准备好的语句,它只覆盖了我一半,迫使我为另一半提供白名单,它无济于事),不是吗?同样安全(并且更容易编码)?如果不是,在哪种输入场景中转义实用程序会失败?
$fields = mysql_escape($fields);
$table = mysql_escape($table);
$age = mysql_escape($age);
$orderby_pref = mysql_escape($orderby_pref);
select $fields from $table where Age='$age' order by $orderby_pref
【问题讨论】:
赞成是因为您了解并关心 sql 注入(而不是大多数人在 php 标记中提问) 动态sql和向查询中添加值是有区别的。绑定参数或字符串连接(加上转义)都可以用于添加值。动态 SQL 需要将可接受的语法列入白名单。因此,要么使用正则表达式、映射或 switch 语句来添加DESC
或 ORDER
和其他限定符。不过,存储过程将是一个冗长的替代方案。
【参考方案1】:
无论您使用准备好的语句还是 mysql
转义函数,您都需要为表名或列名等内容使用白名单。
问题是表名和列名没有用单引号或双引号引起来,所以如果你使用一个专门引用这些字符的函数(当然还有更多......),它不会对你的表做任何事情名字。
考虑表名my_table; DELETE * FROM mysql; SELECT * FROM my_table
。此字符串中的任何内容都不会被 mysql 的转义函数转义,但它绝对是您想要检查白名单的字符串。
除此之外,mysql
转义函数在字符集方面存在问题,这会使它们变得无用,因此最好使用准备好的语句。
【讨论】:
您的my_table; DELETE * FROM mysql; SELECT * FROM my_table
示例确实很好地说明了为什么 MYSQL_ESCAPE 方法会失败......所以,简单来说,SQL 注入保护的唯一替代方案(对于准备好的语句或数据库的转义实用程序没有帮助的部分),是使用白名单方法。这个结论正确吗?【参考方案2】:
您可以使用 PDO,您的生活会变得更轻松...:
# Order
switch(strtoupper($Order))
default:
case 'ASC':
$Order = 'ASC';
break;
case 'DESC':
$Order = 'DESC';
break;
# ID
$ID = 39;
$Username = 'David';
# Query
$Query = $this->DB->Main->prepare('SELECT * FROM Table WHERE ID = :ID AND Username = :Username ORDER BY HellBob '.$Order);
$Query->bindValue(':ID', $ID, PDO::PARAM_INT);
$Query->bindValue(':Username', $Username, PDO::PARAM_STR);
# All good ?
if(!$Query->execute())
exit('Error');
// Results
$Row = $Query->fetch(PDO::FETCH_ASSOC);
您不必担心引号或 SQL 注入。您可以使用您提到的简单“白名单”将变量添加到您的查询中。
【讨论】:
您忘记了订购字段 ;-) 这并不能真正回答问题,因为您在查询中硬编码了表名、列名和排序列。 那么,你可以只使用白名单来写表名。除了白名单,没有安全的方法。以上是关于PHP:使用准备好的语句并防止 SQL 注入与逃逸的主要内容,如果未能解决你的问题,请参考以下文章
在 MySQL 中使用准备好的语句可以防止 SQL 注入攻击吗?