根据PHP官方计划,PHP6正式到来之时,数据库链接方式统一为PDO。但是总有一小撮顽固分子,趁PHP官方还没正式统一时,还用老式的MYSQL驱动链接数据库。即使现在有部分程序改用Mysqli/pdo,只要没用到预编译,均和老式的Mysql驱动没多大区别。在此,我就不点评国内的PHP生态环境了。
回归主题,为什么说PHP必须要用PDO?除了官方要求之外,我认为作为PHP程序员,只要你目前是做开发的话,那么请选择用PDO的程序/框架!PDO除了安全和万金油式数据库链接,还有一点是我目前觉得非常好用的!下面我就用我最近的切身体会来说。
业务环境:公司某老架构,数据库设计的人员太菜了,设计过程完全没有按照数据库范式进行。各种表中使用大量的序列化形式保存(补充:json同理)。
出现问题:销售的客服反馈,网站某用户在编辑地址时,Mysql报错了。
问题猜想:不用说了。肯定是引号,反斜杠引起序列化入库不正常。
下面就来正题,由于涉及到公司的机密。我就模拟一下当时的业务环境。
用户提交的数据以下面数组形式显示:
$array = array( 'firstname' => 'Orthopedics', 'lastname' => "O\"\\s\\'\'\'\''''china\\\'dddddd", 'address' => "Oschina net s'e'x" );
上述数据为了数据的直白性,我加了一些反斜杠了单引号。实际上用户是在地址是输入了类似:Los Angeles Highway's 52。
正常来说,老式的程序都对数据进行addslashes函数过滤处理。数组就是下面这个形式了:
$array = array( 'firstname' => 'Orthopedics', 'lastname' => addslashes("O\"\\s\\'\'\'\''''china\\\'dddddd"), 'address' => addslashes("Oschina net s'e'x") );
上面数据是非常典型的PHP写法了!用户提交什么数据,我都给你过滤。
接着,上文已经说过了,数据库很多地方都以序列化的形式保存。所以,下面插入数据库的代码就类似这样了。
$content = serialize($array); $sql = "INSERT INTO `test`.`test` (`content`) VALUES ('{$content}')"; $res = mysql_query($sql);
恩,很好。看似一个完整的安全过滤系统,什么注入漏洞都跑不过我的法眼。上面的插入语句是正确,并插入了数据!
可是,当系统要读取用户数据时,哈哈!百分百出问题!下面再来段数据读取的代码。
$sql = "select * from test"; $res = mysql_query($sql); $temp = array(); while ($row = mysql_fetch_array($res)) { echo "入库后的序列化: {$row['content']}<br />"; echo "还原序列化数组:<br />"; print_r(unserialize($row['content'])); }
为了给大家验证数据的统一性,我就做了一些数据打印,方便大家理解和阅读。下面就是整个程序的执行结果截图。
很好!从数据库读取序列化保存数据 和 原本的序列化数据 已经不一致了!导致这种现象,是由于数据过滤后,入库时数据库误以为这玩意是用来转义的。
上面这样说太抽象了,来个实例的:数据序列化时,
s:7:"fuck\'u"
上面的fuck\'s 长度为 7。但是入库时,\' 会被转义成 ' !再从库里面读取的话,那么长度已经不一致了。序列化也必然是失败的!
导致这种奇葩的现象,除了数据库设计不当,还有是因为老式的程序架构中,数据都是先做过滤,在入库导致的。当然,如果你不过滤数据,先序列化再入库的话,要是用户提交 反斜杠\ , 依旧可能会出现这种 破问题。
至于要解决这种问题,我有几个方案:
选用PDO。 后文我再说实例。
序列化后的数据,再做base64保存。
序列化的数据尽量剔除掉单引号,反斜杠等歧义字符。
改表结构吧。序列化之类的数据,只用于保存一些不常用的东西(后台数据为主)。
文章最后,我就来介绍本文的主角,PDO。
由于PDO使用预编译的形式进行操作数据库,因此只要不是底层的漏洞(php 5.3.8之前的PDO存在漏洞),可以100%抵挡注入漏洞!具体的说明,看客自行谷歌吧。
得益于PDO的安全机制,在操作刚才的业务环境,用户提交的数据,我们几乎不用作任何处理(注意XSS还是必须的)!下面是基于我写的PDO类库进行操作的代码。
public function fuck() { $array = array( 'firstname' => 'Orthopedics', 'lastname' => "O\"\\s\\'\'\'\''''china\\\'dddddd", 'address' => "Oschina net s'e'x" ); $content = serialize($array); echo '<pre>'; echo "原始数组:<br />"; print_r($array); echo "原始序列化: {$content}<br/>"; $db = $this->db('test'); $result = $db->add(array('content' => $content)); $select = $db->select(); foreach ($select as $key => $value) { echo "入库后的序列化: {$value['content']}<br />"; echo "还原序列化数组:<br />"; print_r(unserialize($value['content'])); } $result = $db->delete(); }
效果截图如下:
无论数据有多恶劣,什么单引号,双引号啊,斜杠,反斜杠。。。PDO均轻易地解决!
文章末尾,用PDO是势在必行的!每一位PHPer,只要你觉得可以,请大胆向领导说,艹,公司架构应该换PDO了!程序员何苦为难程序员呢?有空大家多多看看乌云的漏洞,现在PHP和当年的asp安全形势已经相差不大了,这可是一个危险的警钟!如果你不想PHP成为下一代asp,请从现在开始,数据库都用上PDO吧!顺道吐槽一下:我发现公司的几个网站的代码已经有点变得不可控了。哈哈,太逗了。