2013-12-09 87 views
9

我看到了经常重复的评论“总是使用准备好的查询来防止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); 
从冗长和风格

除此之外,我想用一个比其他什么原因?

回答

2

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

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

也就是说,准备好的查询不容易忽略,通常会导致更好的可维护性。

编辑:也检查@ eggyal的评论,为什么准备好的语句总是注入安全的,而不是逃脱查询参数。

+4

同意,但请注意,因为预备语句的参数从未被服务器解析过SQL,所以通过它们注入SQL是不可能的。而转义方法[已经](http://video.stream/vulnerability/MySQL-SQL-injection-via-multi-byte-characters-5885)(和[仍然受制于](http://stackoverflow.com/) a/12118602))可利用的漏洞,在某些模糊的边缘情况下,可能导致成功的注入攻击。 – eggyal

+0

@eggyal:的确如此。正因如此,如果 - 事实上 - 正如所述的那样恰当地逃脱了,这就是它们在功能上相同的原因。虽然做得好,但通常不是一件容易的事。 – jwueller

+0

在这里,我认为PDO将为每个阅读节省我们所有@eggyal http://stackoverflow.com/a/12118602(** ** Bad **) –

7

一个很好的问题。

逃离

逃逸是更传统的方式做事情。它将反斜线添加到任何不安全的数据中,所以如果有人试图传入直接的SQL语句,它将无害地通过,而不是从字面上理解。这里的问题是有一些边界情况下攻击者仍然可以绕过逃逸函数。其中绝大多数涉及到诸如更改字符集等的奥术技巧。要理解的主要原因是,如果你追求逃避,那就是在所有情况下,对于安全性来说,它都是不确定的。

预处理语句

预处理语句是不是受到相同的缺陷,是因为你打破查询分成两个部分。第一部分包含查询,第二部分包含该查询的参数。因此,MySQL可以以一种不允许注入的安全方式对待不同的参数(至少还没有人找到方法)。

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

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

买者自负

所以准备的语句是100%安全的,对吗?为什么不仅仅使用准备好的语句来处理所有事情我们已经解决了SQL注入问题!好吧,不是。首先需要了解一些注意事项。

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

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

第三个是迄今为止最重要的,您需要在这里密切关注,因为如果您不小心,这可能会对您的程序产生影响。大多数提倡准备语句的人往往会忽略这里最大的缺陷,那就是你对数据库做了更多的查询(关于如何通过CLI执行这些操作,请参考MySQL 5.5 manual)。现在,对于要反复运行相同查询的INSERTUPDATE声明,没有任何比较。准备好的陈述为您节省了很多时间在他们的连接对口上。但是对于SELECT,它变得很不清楚。以我的例子为例。这是通过自动编号字段简单记录的记录。但需要2个查询来运行,而不是一个。如果这是一个低流量的内部应用程序,可能不是什么大问题,但如果该页面每天获得100,000个视图,则可能会降低该页面上的开销。如果是这样,建立一个直接查询可能是一个解决方案。

因此,简而言之

mysqli_real_escape_string有一些缺陷,但可以取得“足够安全”为单查询,只要你了解这些陷阱,不要把它当作你和SQL注入的唯一保障。

mysqli_prepare总是安全的,但也做了更多的查询,所以它可能不会对您的数据库和/或程序有效。有些疑问应该始终准备好,而有些则可能不应该。

其它解决方案还可以有用的,而不是使用任何一个解决方案

  • Typecasting(有一定的局限性,但没有与作出SQL不安全)
  • 白名单(在那里你保证,通过PHP,会是什么值可接受并且只允许这些值)

最后,由您决定哪里是合适的,哪里可以更好地使用另一个。

+1

+1。非常好的深度答案。 – jwueller

+0

不幸的是,在转义部分它失败了。坦率地说,这部分完全没有意义。 –

+0

而且在“很多时间”部分。 –

-2

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

“构建”有两个严重缺陷。

首先,“构建”你所谈论的是手动。意味着它是开放的,以两胁:

  • 它使格式化可拆卸。一个聪明的程序员总是会试图“优化”代码......导致在代码的早些时候在其他地方重复调用quote()来使该过程自动化。这将直接导致灾难,因为在一个大项目中,忽略一个或两个变量的机会非常大,并且使其不受格式化。
  • 手动格式化总是取决于程序员的技巧和心情。有些人会这样做,有些人会失败。如果是手动格式,则无法保证格式正确。

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

其次,SQL语法不限于字符串只。说,你的quote()函数将不会与LIMIT子句参数一起使用。所以你必须设计别的东西(或者简单地让注入)。

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