当提到防止SQL注入的办法时,脑海中总是会想到使用PDO绑定参数的办法或者使用mysql_real_eascape_string()来处理(虽然古老的 mysql_XXX 这类的函数已经不建议使用)。但是PDO是如何防止注入的呢?
在手册中,有这样一段:
Many of the more mature databases support the concept of prepared statements. What are they? They can be thought of as a kind of compiled template for the SQL that an application wants to run, that can be customized using variable parameters. Prepared statements offer two major benefits:
Prepared statements are so useful that they are the only feature that PDO will emulate for drivers that don’t support them. This ensures that an application will be able to use the same data access paradigm regardless of the capabilities of the database.
大概的翻译是:
很多更成熟的数据库都支持预处理语句的概念。这些是什么?它可以被认为是作为一种通过编译SQL语句模板来运行sql语句的机制。预处理语句可以带来两大好处:
预处理语句非常有用,PDO可以使用一种本地模拟的办法来为没有预处理功能的数据库系统提供这个功能。这保证了一个应用可以使用统一的访问方式来访问数据库。
这里讲了使用PDO可以带来两个很好的效果,预编译带来查询速度的提升,变量的绑定可以预防 sql injection,其实PDO的预防sql注入的机制也是类似于使用 mysql_real_escape_string 进行转义,PDO 有两种转义的机制,第一种是本地转义,这种转义的方式是使用单字节字符集(PHP < 5.3.6)来转义的( 单字节与多字节 ),来对输入进行转义,但是这种转义方式有一些 隐患 。隐患主要是:在PHP版本小于5.3.6的时候,本地转义只能转换单字节的字符集,大于 5.3.6 的版本会根据 PDO 连接中指定的 charset 来转义。PHP官方手册这里有 说明 :
The method in the below example can only be used with character sets that share the same lower 7 bit representation as ASCII, such as ISO-8859-1 and UTF-8. Users using character sets that have different representations (such as UTF-16 or Big5) must use the charset option provided in PHP 5.3.6 and later versions.
所以就是说, 不同的版本的PDO 在本地转义的行为上是有区别的。
第二种方式是PDO,首先将 sql 语句模板发送给Mysql Server,随后将绑定的字符变量再发送给Mysql server,这里的转义是在Mysql Server做的,它是根据你在连接PDO的时候,在charset里指定的编码格式来转换的。这样的转义方式更健全,同时还可以在又多次重复查询的业务场景下,通过复用模板,来提高程序的性能。如果要设置Mysql Server 来转义的话,就要首先执行:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
下面是通过 wireshark 抓到的数据包,来具体显示PDO 查询的过程:
绑定的变量:
如果不执行 $pdo ->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); PDO 只是会将插入的参数使用本地转义之后和SQL模板拼装起来,然后一起发送给Mysql Server。这实际上与使用mysql_real_escape_string()过滤,然后拼装这种做法并没有什么不同。
要对数据库的安全做出更加全面的考量,以下两种方式任选其一:
A. 通过添加(php 5.3.6以前版本):$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
B. 升级到php 5.3.6 (不用设置PDO::ATTR_EMULATE_PREPARES也可以)
为了程序移植性和统一安全性,建议使用$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false)方法