ThinkPHP 3.1中的SQL注入漏洞分析----论ThinkPHP 3.1中的半吊子的PDO封装
我总结ThinkPHP的PDO封装可以用买椟还珠来下结论,表面上封装了PDO支持,但实际却并没有使用到PDO的精髓部分,这不是买椟还珠是什么呢?
花了一些时间了解到ThinkPHP 3.1框架,其官方网站上对其描述得相当不错,但随着我阅读其代码,事实并不是想象的那么好,特别是PDO封装这一部分,处理得相当糟糕,远不如使用原生态的PDO安全, 只是简单地使用addslashes处理用户提交的数据,并没有使用到PDO的精髓部分:prepare预处理和参数化查询。这样其实带来的SQL注入的风险更大,建议对安全性要求较高的环境,不要使用addslashes处理用户提交的内容,如有可能不要使用ThinkPHP框架了,请使用PDO参数化查询从根本上杜绝SQL注入,或使用安全性处理更好的Yii 框架。
首先,分析为何addslashes会在特定条件下造成注入漏洞
请阅读以下几个文章,可得知为何变量使用addslashes处理后仍然造成SQL注入,
PHP字符编码绕过漏洞--addslashes、mysql_real_escape漏洞
http://blog.csdn.net/zhuizhuziwo/article/details/8525789
addslashes:会导致SQL注入漏洞
http://blog.csdn.net/felio/article/details/1226569
可以说,从这点上来看,ThinkPHP框架自吹的水分很多,远不如想象的那么好。另外 PHPCMS v9网上暴露的SQL注入漏洞超多,也是因为其依赖addslashes防止SQL注入(结果证明漏洞更多)。
另外,为了从根本上防止SQL注入,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).
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.
也就是说,PHP官方强烈推荐使用PDO的prepare机制,使用参数化查询,即SQL查询模板与变量分离提交,使用bindValue, bindParam的方式处理用户提交数据,一方面可提升性能,另一方面可从根本上杜绝SQL注入问题。
但很可惜的是 ThinkPHP仍然使用addslashes方式处理变量,可见其处理方法极其陈旧,充满SQL注入的风险。
事实上,这套框架在此方面处理的相当糟糕。
让我们追踪ThinkPHP代码,找到其处理数据的方式
Model::save($data='',$options=array())方法用于保存数据,实际上调用了 $this->db->update($data,$options)
再观察DbPdo::update()方法,由Db类继承而来,关键点是:$this->parseSet($data)
protected function parseSet($data) {
foreach ($data as $key=>$val){
$value = $this->parseValue($val);
if(is_scalar($value)) // 过滤非标量数据
$set[] = $this->parseKey($key).'='.$value;
}
return ' SET '.implode(',',$set);
}
parseValue方法中,关键点是escapeString, DbPdo的方法中:
public function escapeString($str) {
switch($this->dbType) {
case 'PGSQL':
case 'MSSQL':
case 'SQLSRV':
case 'MYSQL':
return addslashes($str);
case 'IBASE':
case 'SQLITE':
case 'ORACLE':
case 'OCI':
return str_ireplace("'", "''", $str);
}
}
即简单地使用addslashes处理输入变量。
我觉得ThinkPHP中虽然提供了PDO的支持,但实际上还是使用了addslashes方式处理变量,丝毫没有使用到PDO的精髓部分, prepare预处理SQL, bindValue, bindParam绑定输入变量。
我认为这只是自欺欺人的做法,表面上声称支持PDO,但完全是金玉其外,败絮其中。 可以哄骗一些小白程序员而已,并不到达到其标称的安全性目标。
如果您使用了ThinkPHP框架,我只能认为你相当的不幸,并默默为你祈祷你的站点没有受到SQL注入攻击了。
那么,如何处理这个问题却是留给我们的重大问题,如果你的项目使用了ThinkPHP,这时候修改框架或是换框架是相当不现实的,那么只能从扩展ThinkPHP的数据库访问层类入手,对其进行处理了。
处理办法如下:
重写ThinkPHP的数据库访问层
1. 在项目目录 lib中新建立文件DbPdoMysql.class.php, 在其中定义DbPdoMysql类,并实际crud的四大方法, 如:
class DbPdoMysql extends DbPdo {
public function delete($sql, array $params = array() ) {
}
public function update(array $data, array $options=array(), array $params=array()){
}
public function insert($data,$options=array(),$replace=false, array $params=array() ) {
}
public function select($options=array(), array $params=array() ) {
}
}
然后,具体使用PDO作参数化查询的处理,请查阅PHP手册和ThinkPHP框架的数据库处理流程。
然后,再建立新类,继承自Moble,并重写其四大CRUD处理,大致如下:
class DbPdoMysql extends DbPdo {
public function delete($conditions='', array $params = array() ) {
}
public function update(array $data, $conditions='', array $params=array()){
}
public function insert(array $data,$options=array(),$replace=false) {
}
public function select($options=array(), array $params=array() ) {
}
}
然后,我们在使用模型进行数据库操作时,需要特殊处理一下,不能使用旧的方式处理数据库,以免造成更严重的SQL注入问题。
查询数据时:
$model -> select('where id = ? And name =?', array($_GET['id'], $_GET['name']) )
删除数据时:
$model->delete('where id = ?', array($_GET['id']))
插入数据时使用以前代码即可(DbPdoMysql类中应该完成参数化处理)
强烈建议您根据本文提供的思路,对使用ThinkPHP的项目进行安全处理,以免造成潜在的安全隐患。
另外,关于PDO的防注入原理分析及使用注意事项,请参阅我的文章
PDO防注入原理分析以及使用PDO的注意事项
http://zhangxugg-163-com.iteye.com/blog/1835721
若有异议,请联系笔者信箱 zhangxugg@163.com, 欢迎探讨交流。