前言
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等方法产生注入的原理也是和上文一样的,具体过程就不再分析。