前言
2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp3系列进行了一处安全更新,经过分析,此次更新修正了由于select()
,find()
,delete()
方法可能会传入数组类型数据产生的多个sql注入隐患。
thinkphp3的github地址为:https://github.com/top-think/thinkphp
下载完毕后需要使用git checkout
命令回退到上一次的提交:
git checkout 109bf30 |
查看本次更新的提交记录:
可看到此次更新主要是在ThinkPHP/Library/Think/Model.class.php
文件中,其中对于delete
,find
,select
三个函数进行了修改。思考可能是因为$option
参数可控,然后经过_parseOptions
_函数处理过后产生了注入。
环境搭建
将下载的tp3框架放入web服务器的根目录下,然后在/Application/Home/Controller/IndexController.class.php
中写一段测试代码:
新建test
函数用来测试tp3的find
方法,在本地的mysql数据库中新建tptest数据库,然后新建user表,并添加测试数据:
访问http://127.0.0.1:8888/index.php?m=Home&c=Index&a=test&id=1
查看结果:
看到以上结果证明环境搭建成功。
注入分析
根据网上给的poc看下结果,poc为:
http://127.0.0.1:8888/index.php?m=Home&c=Index&a=test&id[where]=1 and updatexml(1,concat(0x7e,database(),0x7e),1) |
成功执行sql语句:
使用xdeug看下调用过程:
在此处打断点:
进入M
方法:
进入find
方法:
看下面这个判断:
_if (isnumeric(_$options) || is_string($options)) { |
由于现在的$option
是个数组,所以并不会进入这个判断,继续往下:
getPk
函数是查找mysql主键的函数,继续往下会有一个判断:
_if (isarray(_$options) && (count($_options) > 0) && isarray($pk)) { |
必须同时满足
$option是一个数组并且
$option数组中的元素大于0并且查询出的主键
$pk是一个数组才会进入判断,显然这里不满足
$pk是一个数组的条件,所以不会进入循环。继续往下:
来到_parseOptions
_函数,进入此函数:
使用_arraymerge
_函数将
$option与option合并,合并结果还是
$option,因为``$this->option是一个空数组。继续往下:
if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { |
分析这个判断,发现$options['where']
并不是一个数组,所以不会进入判断,继续往下:
可以看到又一个表达式过滤函数_optionsfilter
_,进入这个函数:
发现是个空函数,所以不会进行任何过滤,继续往下:
开始调用tp的select方法在数据库中查找数据,进入到select方法中可看到查询的sql语句还是我们拼接过后的,所以就导致了sql注入的产生:
产生注入的原因
为什么tp没有对我们传入的数据进行过滤呢?带着这个疑问我们走一遍正常的流程,将poc换为:
http://127.0.0.1:8888/index.php?m=Home&c=Index&a=test&id=1 and updatexml(1,concat(0x7e,database(),0x7e),1) |
和之前一样的流程就不截图了,走到这里的时候不一样了:
由于此时的$options['where']
是一个数组了,所以会进入判断:
然后进过_parseType
_方法处理:
进过inrval
函数处理后在输入的sql语句就是正常的了,这样就不会产生注入
所以导致注入产生的原因是构造的poc绕过了tp对$option['where']
是否是一个数组的判断,从而不会进入循环,也不会经_parseType
_函数处理从而导致了注入。
官方的修复方法是:
在_parseOptions
_函数处理时不传入
$option,这样经过
_parseOptions处理过后,
$option始终为空,也就是我们传入的poc执行后的sql语句就变成了:
_select from users limit 1_ |
无论你查询什么都只会返回第一条数据,这种处理方式还是挺暴力的。
最后
本次注入的产生不只有find方法,select,delete等方法产生注入的原理也是和上文一样的,具体过程就不再分析。