`
月影无痕
  • 浏览: 1003272 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PDO防注入原理分析以及使用PDO的注意事项

 
阅读更多

 

我们都知道,只要合理正确使用PDO,可以基本上防止SQL注入的产生,本文主要回答以下两个问题:

为什么要使用PDO而不是mysql_connect?

为何PDO能防注入?

使用PDO防注入的时候应该特别注意什么?

 

一、为何要优先使用PDO?

PHP手册上说得很清楚:

Prepared statements and stored procedures
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:

The query only needs to be parsed (or prepared) once, but can be executed multiple times with the same or different parameters. When the query is prepared, the database will analyze, compile and optimize its plan for executing the query. For complex queries this process can take up enough time that it will noticeably slow down an application if there is a need to repeat the same query many times with different parameters. By using a prepared statement the application avoids repeating the analyze/compile/optimize cycle. This means that prepared statements use fewer resources and thus run faster.

 


The parameters to prepared statements don't need to be quoted; the driver automatically handles this. If an application exclusively uses prepared statements, the developer can be sure that no SQL injection will occur (however, if other portions of the query are being built up with unescaped input, SQL injection is still possible).

 

即使用PDO的prepare方式,主要是提高相同SQL模板查询性能、阻止SQL注入

同时,PHP手册中给出了警告信息

Prior to PHP 5.3.6, this element was silently ignored. The same behaviour can be partly replicated with the PDO::MYSQL_ATTR_INIT_COMMAND driver option, as the following example shows.
Warning

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.

 

意思是说,在PHP 5.3.6及以前版本中,并不支持在DSN中的charset定义,而应该使用PDO::MYSQL_ATTR_INIT_COMMAND设置初始SQL, 即我们常用的 set names gbk指令。

 

我看到一些程序,还在尝试使用addslashes达到防注入的目的,殊不知这样其实问题更多, 详情请看http://www.lorui.com/addslashes-mysql_escape_string-mysql_real_eascape_string.html

还有一些做法:在执行数据库查询前,将SQL中的select, union, ....之类的关键词清理掉。这种做法显然是非常错误的处理方式,如果提交的正文中确实包含 the students's union , 替换后将篡改本来的内容,滥杀无辜,不可取。

 

二、为何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.4+,php 5.3.8存在致命的hash碰撞漏洞。

 

2. 若使用php 5.3.6+, 请在在PDO的DSN中指定charset属性

3. 如果使用了PHP 5.3.6及以前版本,设置PDO::ATTR_EMULATE_PREPARES参数为false(即由MySQL进行变量处理),php 5.3.6以上版本已经处理了这个问题,无论是使用本地模拟prepare还是调用mysql server的prepare均可。在DSN中指定charset是无效的,同时set names <charset>的执行是必不可少的。

 

4. 如果使用了PHP 5.3.6及以前版本, 因Yii框架默认并未设置ATTR_EMULATE_PREPARES的值,请在数据库配置文件中指定emulatePrepare的值为false。

 

那么,有个问题,如果在DSN中指定了charset, 是否还需要执行set names <charset>呢?

是的,不能省。set names <charset>其实有两个作用:

A.  告诉mysql server, 客户端(PHP程序)提交给它的编码是什么

B.  告诉mysql server, 客户端需要的结果的编码是什么

也就是说,如果数据表使用gbk字符集,而PHP程序使用UTF-8编码,我们在执行查询前运行set names utf8, 告诉mysql server正确编码即可,无须在程序中编码转换。这样我们以utf-8编码提交查询到mysql server, 得到的结果也会是utf-8编码。省却了程序中的转换编码问题,不要有疑问,这样做不会产生乱码。

 

那么在DSN中指定charset的作用是什么? 只是告诉PDO, 本地驱动转义时使用指定的字符集(并不是设定mysql server通信字符集),设置mysql server通信字符集,还得使用set names <charset>指令。

 

如果图片丢失,可以发邮件至zhangxugg@163.com, 索取PDF版本。

 

我真想不通,一些新的项目,为何不使用PDO而使用传统的mysql_XXX函数库呢?如果正确使用PDO,可以从根本上杜绝SQL注入,我强烈建议各个公司的技术负责人、一线技术研发人员,要对这个问题引起重视,尽可能使用PDO加快项目进度和安全质量。

 

不要再尝试自己编写SQL注入过滤函数库了(又繁琐而且很容易产生未知的漏洞)。

 

 

 

  • 大小: 19.5 KB
  • 大小: 65.2 KB
  • 大小: 76.3 KB
  • 大小: 99.8 KB
14
4
分享到:
评论
12 楼 fanyilong_v5 2014-09-03  
哈哈,现在写的框架就是用的PDO,再说貌似以后7开始的数据库api都是PDO。
11 楼 月影无痕 2014-04-26  
liangjian1024 写道
PDO的效率要略低于mysql_XXX,你可以试试


php新版中,将舍弃掉对mysql的支持。
10 楼 liangjian1024 2014-04-23  
PDO的效率要略低于mysql_XXX,你可以试试
9 楼 月影无痕 2013-05-02  
xiaogui32 写道
我想请问下,您应该有去研究一下phpcms v9的代码吧。如果我们要改造成PDO的要怎么改?改造phpcms/libs/classes/mysql.class.php吗?
但问题是,很多模块没有使用"select * from info where id =? and name = ?" 这样的代替符,在数据层要怎么处理?



我正在实现phpcms v9的PDO数据访问层,正在进行测试。测试成功之后,会发布出来的。
8 楼 xiaogui32 2013-04-30  
我想请问下,您应该有去研究一下phpcms v9的代码吧。如果我们要改造成PDO的要怎么改?改造phpcms/libs/classes/mysql.class.php吗?
但问题是,很多模块没有使用"select * from info where id =? and name = ?" 这样的代替符,在数据层要怎么处理?
7 楼 huangdj 2013-03-26  
这就是神奇之处,可见这次PHP是将SQL模板和变量是分两次发送给MySQL的,由MySQL完成变量的转义处理,既然变量和SQL模板是分两次发送的,那么就不存在SQL注入的问题了
===================================
我比较钝愚,楼主能详细解释一下 分两次就没有注入问题?
$st->bindParam(1,$id);$st->bindParam(2,$name); $st->execute(); 我原理上不明白,感觉是参数化后,数据库进行了怎样执行?我个人认为这个才是防止sql注入关键,请多多指教啊!
6 楼 huangdj 2013-03-26  
这就是神奇之处,可见这次PHP是将SQL模板和变量是分两次发送给MySQL的,由MySQL完成变量的转义处理,既然变量和SQL模板是分两次发送的,那么就不存在SQL注入的问题了
5 楼 fyland 2013-03-26  
月影无痕 写道
fyland 写道
php 5.4+ 哪里不稳定了?能否给出测试代码?


看来朋友你对这个新版本研究还是相当多的,你这么一说,我倒是很有兴趣也想反复测试一下,尝试将其应用到生产环境中。

那么,针对php 5.4+, 你的php-fpm.log中是否发现过大量错误日志?

我的php-fpm.log中出现大量seg fault之类的错误,导致我不得不暂停生产环境中使用它。

有,但不是“大量”的,当时用最新版的重新编译了一下就没再出现过!
4 楼 月影无痕 2013-03-25  
fyland 写道
php 5.4+ 哪里不稳定了?能否给出测试代码?


看来朋友你对这个新版本研究还是相当多的,你这么一说,我倒是很有兴趣也想反复测试一下,尝试将其应用到生产环境中。

那么,针对php 5.4+, 你的php-fpm.log中是否发现过大量错误日志?

我的php-fpm.log中出现大量seg fault之类的错误,导致我不得不暂停生产环境中使用它。
3 楼 fyland 2013-03-25  
月影无痕 写道
fyland 写道
php 5.4+ 哪里不稳定了?能否给出测试代码?


和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要好一些。
2 楼 月影无痕 2013-03-25  
fyland 写道
php 5.4+ 哪里不稳定了?能否给出测试代码?


和APC, libmemcached模块兼容性很不好

另一方面php-fpm.log日志中,也出现大量报seg fault 之类错误,这时前端nginx就报502之类的错误。
1 楼 fyland 2013-03-25  
php 5.4+ 哪里不稳定了?能否给出测试代码?

相关推荐

    PDO防注入原理分析以及使用PDO的注意事项总结

    本文详细讲述了PDO防注入原理分析以及使用PDO的注意事项,分享给大家供大家参考。具体分析如下: 我们都知道,只要合理正确使用PDO,可以基本上防止SQL注入的产生,本文主要回答以下两个问题: 为什么要使用PDO而不是...

    PDO防注入原理分析以及注意事项

    主要介绍了PDO防注入原理分析以及使用PDO的注意事项,十分详细,分析的也很到位,需要的朋友可以参考下

    使用PDO防sql注入的原理分析

    主要给大家介绍了关于使用PDO防sql注入的原理的相关资料,文中还给大家介绍了使用PDO的注意事项,通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧

    PHP使用PDO实现mysql防注入功能详解

    主要介绍了PHP使用PDO实现mysql防注入功能,结合实例形式详细分析了PHP使用pdo操作mysql防注入原理、实现方法及相关注意事项,需要的朋友可以参考下

    PDO SDO应用实例分析,让人茅塞顿开!

    PDO SDO应用实例分析,让人茅塞顿开!

    CoDeSys3.5 PDO Demo

    我自己写的CoDeSys3.5的PDO通信的Demo 下载就可以用 可是本人呕心沥血完成之作,现奉献大家。在此基础上只需稍作修改就可以直接使用

    PDO SDO应用实例分析

    清楚的说明CANOPEN数据传输是怎么一回事,帮助理解CANOPEN协议. 学习CANOPEN的好文档

    php pdo组件的用法

    单条查询不需要启用事务处理,并且不需要使用PDO的预处理方式,但注意要使用$pdo-&gt;quote()方法来自动将字符型变量值的字符中首尾加上单引号,以防止SQL注入,并且免去特殊符号转义的过程。 $db_type='mysql'; //...

    数据库抽象层-PDO和ADOdb.doc

    能使用PDO扩展本身执行任何数据库操作,必须使用一个database-specific PDO driver(针对特定数据库的PDO驱动)访问数据库 PDO开启:修改php.ini文件,extension=php_pdo.dll,分号删除就是开启PDO 另外还有PDO其他...

    PHP中PDO的简单使用

    PDO(PHP Data Object) 是PHP 5新出来的东西,在PHP 6都要出来的时候,PHP 6只默认使用PDO来处理数据库,将把所有的数据库扩展移到了PECL,那么默认就是没有了我们喜爱的php_mysql.dll之类的了,那怎么办捏,我们只有...

    canopen实例分析之PDO

    以简单的实例,透彻讲解PDO的收发过程。同济大学。

    php5.4 使用pdo方式连接sqlserver所使用的dll文件

    php使用pdo方式连接sqlserver所要使用的扩展dll,是32位的,分线程安全和非安全。

    pdo pdo封装函数

    pdo链接数据库封装类库,用于数据库操作,执行数据库curd操作

    pdo_mysql.rar_PDO_pdo_mysql.so

    php常用拓展,使用PDO链接数据库时必须用到

    EtherCAT增加PDO读写寄存器

    EtherCAT增加PDO读写寄存器

    欧姆龙PLC总线PDO映射松下驱动器教程分享

    本人从事自动化十多年,积累的心得,今天分享欧姆龙PLC总线PDO映射松下驱动器教程,(具体程序下载链接,请翻阅本人下载栏目中的博客,本人VX:zhonglovexl)如您需要或者了解更多种类PLC的功能块,请翻阅本人其他...

Global site tag (gtag.js) - Google Analytics