PDO防流入原理分析以及使用PDO的注意事项
我们都知道,只要合理正确使用PDO,可以基本上防止SQL注入的产生,本文主要回答以下两个问题:
为何PDO能防注入?
使用PDO防注入的时候应该特别注意什么?
一、为何PDO能防SQL注入?
请先看以下PHP代码:
<?php
$pdo = new PDO("mysql:host=192.168.0.1;dbname=test;charset=utf8","root");
$st = $pdo->prepare("select * from info where id =? and name = ?");
$id = 21;
$name = 'zhangsan';
$st->bindParam(1,$id);
$st->bindParam(2,$name);
$st->execute();
$st->fetchAll();
?>
环境如下:
PHP 5.4.7
Mysql 协议版本 10
MySQL Server 5.5.27
为了彻底搞清楚php与mysql server通讯的细节,我特别使用了wireshark抓包进行研究之,安装wireshak之后,我们设置过滤条件为tcp.port==3306, 如下图:
如此只显示与mysql 3306端口的通信数据,避免不必要的干扰。
特别要注意的是wireshak基于wincap驱动,不支持本地环回接口的侦听(即使用php连接本地mysql的方法是无法侦听的),请连接其它机器(桥接网络的虚拟机也可)的MySQL进行测试。
然后运行我们的PHP程序,侦听结果如下,我们发现,PHP只是简单地将SQL直接发送给MySQL Server :
其实,这与我们平时使用mysql_real_escape_string将字符串进行转义,再拼接成SQL语句没有差别(只是由PDO本地驱动完成转义的),显然这种情况下还是有可能造成SQL注入的,也就是说在php本地调用pdo prepare中的mysql_real_escape_string来操作query,使用的是本地单字节字符集,而我们传递多字节编码的变量时,有可能还是会造成SQL注入漏洞(php 5.3.6以前版本的问题之一,这也就解释了为何在使用PDO时,建议升级到php 5.3.6+,并在DSN字符串中指定charset的原因。
针对php 5.3.6以前版本,以下代码仍然可能造成SQL注入问题:
$pdo->query('SET NAMES GBK');
$var = chr(0xbf) . chr(0x27) . " OR 1=1 /*";
$query = "SELECT * FROM info WHERE name = ?";
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));
原因与上面的分析是一致的。
而正确的转义应该是给mysql Server指定字符集,并将变量发送给MySQL Server完成根据字符转义。
那么,如何才能禁止PHP本地转义而交由MySQL Server转义呢?
PDO有一项参数,名为PDO::ATTR_EMULATE_PREPARES ,表示是否使用PHP本地模拟prepare,此项参数默认值未知。而且根据我们刚刚抓包分析结果来看,php 5.3.6+默认还是使用本地变量转,拼接成SQL发送给MySQL Server的,我们将这项值设置为false, 试试效果,如以下代码:
<?php
$pdo = new PDO("mysql:host=192.168.0.1;dbname=test;","root");
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$st = $pdo->prepare("select * from info where id =? and name = ?");
$id = 21;
$name = 'zhangsan';
$st->bindParam(1,$id);
$st->bindParam(2,$name);
$st->execute();
$st->fetchAll();
?>
红色行是我们刚加入的内容,运行以下程序,使用wireshark抓包分析,得出的结果如下:
看到了吗?这就是神奇之处,可见这次PHP是将SQL模板和变量是分两次发送给MySQL的,由MySQL完成变量的转义处理,既然变量和SQL模板是分两次发送的,那么就不存在SQL注入的问题了,但需要在DSN中指定charset属性,如:
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root');
如此,即可从根本上杜绝SQL注入的问题。如果你对此不是很清楚,可以发邮件至zhangxugg@163.com, 一起探讨。
二、使用PDO的注意事项
知道以上几点之后,我们就可以总结使用PDO杜绝SQL注入的几个注意事项:
1. php升级到5.3.6+,生产环境强烈建议升级到php 5.3.9,php 5.3.8有致命的hash碰撞漏洞,php 5.4+目前又相当不稳定(反复测试的结论,不建议在生产环境中使用)
2. 在PDO的DSN中指定charset属性
3. 最重要的要点,设置PDO::ATTR_EMULATE_PREPARES参数为false。
4. Yii框架默认并未设置ATTR_EMULATE_PREPARES的值,请在数据库配置文件中指定emulatePrepare的值为false。
如果图片丢失,可以发邮件至zhangxugg@163.com, 索取PDF版本。
我真想不通,一些新的项目,为何不使用PDO而使用传统的mysql_XXX函数库呢?如果正确使用PDO,可以从根本上杜绝SQL注入,我强烈建议各个公司的技术负责人、一线技术研发人员,要对这个问题引起重视,尽可能使用PDO加快项目进度和安全质量。
不要再尝试自己编写SQL注入过滤函数库了(又繁琐而且很容易产生未知的漏洞)。
和APC, libmemcached模块兼容性很不好
另一方面php-fpm.log日志中,也出现大量报seg fault 之类错误,这时前端nginx就报502之类的错误。
和APC, libmemcached模块兼容性很不好
另一方面php-fpm.log日志中,也出现大量报seg fault 之类错误,这时前端nginx就报502之类的错误。
APC不太清楚,因为看过几个关于APC、xcache、eAccelerator的测试,貌似eAccelerator快一点,所有一直都是用eAccelerator。倒是libmemcached一直在用,在PHP 5.4.1X中没出现过什么问题。
你所说的seg fault错误不知道是不是这个bug导致的https://bugs.php.net/bug.php?id=62836,如果是的话,这个BUG早已修复。
PHP 5.4相比PHP 5.3更加激进,除了新增了一些语法特性外,更好的支持面向对象。更重要的是,正是因为激进,不再兼容一些老的特性,性能有很大的提升,而内存使用率略有减小。官方的说法是:
“许多内部结构已变得更小或完全消失,从而在大型 PHP 应用程序中可节省 20-50% 的内存。通过各种优化使性能提高 10-30%(主要取决于代码执行的操作),这些优化包括内联各种常见代码路径、将 $GLOBALS 添加到 JIT、“@”操作符运算更快、添加了运行时类/函数/常量缓存、运行时字符串常量现在被拘留、通过预先计算的散列更快地访问常量、空数组速度更快并使用更少内存、unserialize() 和 FastCGI 请求处理速度更快,以及在整个代码中进行更多的内存和性能调整。
例如,早期的一些测试表明,Zend Framework 在 5.4 中运行速度提高 21% 并且内存使用减少 23%,而 Drupal 内存使用减少 50% 并且运行速度大约提高 7%。”
我去年自己的测试数据虽然没有像官方说的那样漂亮,但在性能和内存使用上 PHP5.4 确实比PHP5.3要好一些。
看来朋友你对这个新版本研究还是相当多的,你这么一说,我倒是很有兴趣也想反复测试一下,尝试将其应用到生产环境中。
那么,针对php 5.4+, 你的php-fpm.log中是否发现过大量错误日志?
我的php-fpm.log中出现大量seg fault之类的错误,导致我不得不暂停生产环境中使用它。
看来朋友你对这个新版本研究还是相当多的,你这么一说,我倒是很有兴趣也想反复测试一下,尝试将其应用到生产环境中。
那么,针对php 5.4+, 你的php-fpm.log中是否发现过大量错误日志?
我的php-fpm.log中出现大量seg fault之类的错误,导致我不得不暂停生产环境中使用它。
有,但不是“大量”的,当时用最新版的重新编译了一下就没再出现过!