准备查询与构造查询

时间:2013-12-09 00:08:35

标签: php mysql mysqli prepared-statement sql-injection

我看到经常重复的注释"总是使用准备好的查询来防止SQL注入攻击"。

使用准备好的查询和构造查询之间的实际区别是什么?用户输入总是被清理?

构建

function quote($value) {
  global $db; 
  return "'" . mysqli_real_escape_string($db, $value) . "'";
}

$sql = "INSERT INTO foo (a, b) VALUES (" . quote($a) . "," . quote($b) . ")";

制备

$stmt = mysqli_prepare($db, "INSERT INTO foo (a, b) VALUES (?, ?)");
mysqli_stmt_bind_param($stmt, "ss", $a, $b);

除了冗长和风格之外,我还有什么理由想要使用另一种?

3 个答案:

答案 0 :(得分:7)

一个很好的问题。

<强>逃逸

逃避是更传统的做事方式。它会向任何不安全的数据添加反斜杠,因此如果有人试图传入直接的SQL语句,它将无害地传递而不是按字面意思传递。这里的问题是,有一些边缘情况,攻击者仍然可以绕过逃逸功能。其中绝大多数都涉及到诸如改变字符集之类的神秘技巧。要理解的是,如果你追求逃避,最重要的是在所有情况下都不确定安全性。

准备好的陈述

准备好的陈述不会受到同样的瑕疵,因为您将查询分为两部分。第一部分包含查询,第二部分包含该查询的参数。因此,MySQL可以以不安全的方式处理参数,不允许注入(至少没有人找到方法)。

如果需要使用不同的值反复运行相同的查询,准备好的语句可以节省大量时间。 MySQL将语句存储在内存中,因此多次执行非常快。

但是,您无法参数化查询的某些部分(即表名)。

警告Emptor

如此准备好的陈述是100%安全的,对吧?为什么不直接使用准备好的陈述?我们已经解决了SQL注入FOREVER!嗯,不太好。首先需要了解一些警告。

第一个特定于mysqli。您需要安装MySQL Native Drivermysqlnd)来执行返回mysqli结果的预准备语句。具体而言,mysqli_stmt_get_result,返回与mysqli_query类似的结果集。某些共享主机和较旧的服务器仍在使用较旧的MySQL客户端库。如果您想知道自己在使用哪个,请运行phpinfo()并查找mysqlnd块。即使您没有使用预准备语句,mysqlnd也是一个好主意,因为它是由PHP团队专门为了充分利用MySQL而构建的。但是,有些人无法更改驱动程序,因为这是服务器配置级别的更改。

第二个是针对那些使用PDO的人(用PHP连接数据库的另一种方式)。现在,PDO有一种更好的处理预处理语句的方法(它有一个方便的别名系统),并且不需要mysqlnd来做预备语句。这里需要注意的是,默认情况下,PDO只模拟准备好的语句,这意味着如果你没有正确配置它,你所做的只是使用一个库来为你进行转义。有关详细信息,请参阅PDO::ATTR_EMULATE_PREPARES下的this page

第三个是迄今为止最重要的一个,你需要在这里密切关注,因为如果你不小心,这会对你的程序产生影响。大多数提倡准备好的陈述的人倾向于忽略这里最大的陷阱,那就是你在数据库上做了更多的查询(这里是关于如何通过CLI进行查询的MySQL 5.5 manual)。现在,对于INSERTUPDATE语句,您将在其中一遍又一遍地运行相同的查询,这是无法比较的。准备好的语句可以为你的连续对应物节省很多时间。但是对于SELECT,它变得不那么清楚了。以上面的例子为例。它是通过自动编号字段简单地提取记录。但现在需要运行2个查询,而不是一个。如果这是一个流量较低的内部应用程序可能不是什么大问题,但如果该页面每天获得100,000个查看,那么您可能希望降低该页面上的开销。如果是这样,建立一个直接查询可能是一个解决方案。

所以,简而言之

mysqli_real_escape_string有一些陷阱,但只要您了解这些陷阱并且不将其视为您与SQL注入之间的唯一安全性,就可以使单个查询“足够安全”。

mysqli_prepare始终是安全的,但也会进行更多查询,因此对您的数据库和/或程序可能效率不高。有些问题应该总是准备好,而有些问题可能不应该。

其他解决方案也可以使用,而不是使用任何一种解决方案

  • Typecasting(有一些限制,但没有任何与SQL不安全的内容)
  • 白名单(通过PHP确保哪些值可接受,只允许这些值)

最后,由你来决定每个人的适当位置以及使用另一个人的地方可能更好。

答案 1 :(得分:2)

准备好的查询与参数分开发送到SQL服务器,这意味着如果多次执行它们(例如,使用不同的参数),它们只需要编译/优化一次。对于大型数据集,这可能非常重要。

除此之外,它们在功能上是相同的,只要在使用未准备好的查询时输入实际上在所有情况下都被正确转义。

也就是说,准备好的查询不太容易出现疏忽,通常会带来更好的可维护性。

编辑:另请查看@ eggyal关于为什么预处理语句始终是注入安全而不是转义查询参数的注释。

答案 2 :(得分:-2)

好问题。很多人确实忽略了真正的差异。

&#34;构建&#34;有两个严重的缺陷。

首先,&#34;构建&#34;你说的是手册。意味着它可以接受两种威胁:

  • 它使格式可拆卸。聪明的程序员总是想要优化&#34;代码...导致在代码的前面的其他地方移动重复的quote()调用,以自动化该过程。这将直接导致灾难,因为在一个大项目中,有很大的机会忽略一两个变量,并使它们保持未格式化。
  • 手动格式化总是取决于程序员的技能和心情。有些人会做得很好,有些人会失败。如果是手册,则无法保证格式正确。

然而占位符保证了这一点。只要每个变量通过占位符进入查询,您就可以认为自己是安全的。这就是为什么它说:&#34;总是使用准备好的查询来防止SQL注入攻击&#34;,无论它是本机准备好的语句还是只是模拟。使用重要的占位符的想法。

其次, SQL语法不仅限于字符串。比如说,您的quote()函数不能使用LIMIT子句参数。因此,你必须设计其他东西(或者只是让它注入)。

意味着您必须拥有各种SQL文字的占位符Here is my attempt to accomplish the goal - 一个库,可让您拥有所有常用数据类型的占位符。