thinkphp3.2.3 sql注入分析


前言

2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp3系列进行了一处安全更新,经过分析,此次更新修正了由于select(),find(),delete()方法可能会传入数组类型数据产生的多个sql注入隐患。

thinkphp3的github地址为:https://github.com/top-think/thinkphp

下载完毕后需要使用git checkout命令回退到上一次的提交:

git checkout 109bf30

查看本次更新的提交记录

20200607132038

可看到此次更新主要是在ThinkPHP/Library/Think/Model.class.php文件中,其中对于deletefindselect三个函数进行了修改。思考可能是因为$option参数可控,然后经过_parseOptions函数处理过后产生了注入。

环境搭建

将下载的tp3框架放入web服务器的根目录下,然后在/Application/Home/Controller/IndexController.class.php中写一段测试代码:

20200607163720

新建test函数用来测试tp3的find方法,在本地的mysql数据库中新建tptest数据库,然后新建user表,并添加测试数据:

20200607164204

访问http://127.0.0.1:8888/index.php?m=Home&c=Index&a=test&id=1查看结果:

20200607164514

看到以上结果证明环境搭建成功。

注入分析

根据网上给的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语句:

20200607164941

使用xdeug看下调用过程:

在此处打断点:

20200607165202

20200607165252

20200607165350

进入M方法:

20200607165511

进入find方法:

20200607165638

看下面这个判断:

        if (is_numeric($options) || is_string($options)) {
            $where[$this->getPk()] = $options;
            $options               = array();
            $options['where']      = $where;
        }

由于现在的$option是个数组,所以并不会进入这个判断,继续往下:

20200607165942

getPk函数是查找mysql主键的函数,继续往下会有一个判断:

if (is_array($options) && (count($options) > 0) && is_array($pk)) {
            // 根据复合主键查询
            $count = 0;
            foreach (array_keys($options) as $key) {
                if (is_int($key)) {
                    $count++;
                }

            }
            if (count($pk) == $count) {
                $i = 0;
                foreach ($pk as $field) {
                    $where[$field] = $options[$i];
                    unset($options[$i++]);
                }
                $options['where'] = $where;
            } else {
                return false;
            }

必须同时满足$option是一个数组并且$option数组中的元素大于0并且查询出的主键$pk是一个数组才会进入判断,显然这里不满足$pk是一个数组的条件,所以不会进入循环。继续往下:

20200607170544

来到_parseOptions函数,进入此函数:

20200607170706

使用array_merge函数将$option与option合并,合并结果还是$option,因为$this->option是一个空数组。继续往下:

20200607171326

if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
            // 对数组查询条件进行字段类型检查
            foreach ($options['where'] as $key => $val) {
                $key = trim($key);
                if (in_array($key, $fields, true)) {
                    if (is_scalar($val)) {
                        $this->_parseType($options['where'], $key);
                    }
                }
            }
        }

分析这个判断,发现$options['where']并不是一个数组,所以不会进入判断,继续往下:

20200607171630

可以看到又一个表达式过滤函数_options_filter,进入这个函数:

20200607171734

发现是个空函数,所以不会进行任何过滤,继续往下:

20200607171854

开始调用tp的select方法在数据库中查找数据,进入到select方法中可看到查询的sql语句还是我们拼接过后的,所以就导致了sql注入的产生:

20200607172106

产生注入的原因

为什么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)

和之前一样的流程就不截图了,走到这里的时候不一样了:

20200607173325

由于此时的$options['where']是一个数组了,所以会进入判断:

20200607173535

然后进过_parseType方法处理:

20200607173711

进过inrval函数处理后在输入的sql语句就是正常的了,这样就不会产生注入

20200607174119

所以导致注入产生的原因是构造的poc绕过了tp对$option['where']是否是一个数组的判断,从而不会进入循环,也不会经_parseType函数处理从而导致了注入。

官方的修复方法是:
20200607194512
_parseOptions函数处理时不传入$option,这样经过_parseOptions处理过后,$option始终为空,也就是我们传入的poc执行后的sql语句就变成了:

select * from users limit 1

无论你查询什么都只会返回第一条数据,这种处理方式还是挺暴力的。

最后

本次注入的产生不只有find方法,select,delete等方法产生注入的原理也是和上文一样的,具体过程就不再分析。


文章作者: darkless
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 darkless !
评论
 上一篇
你还在忍受龟速下载吗?--mac上使用aria2的最佳实践 你还在忍受龟速下载吗?--mac上使用aria2的最佳实践
首发于freebuf:https://www.freebuf.com/sectool/244962.html aria2简介aria2是一个轻量级多协议和多源命令行下载实用程序。它支持HTTP/HTTPS、FTP、SFTP、BitTor
2020-07-29
下一篇 
蚁剑自定义编码器和解码器来bypass waf 蚁剑自定义编码器和解码器来bypass waf
前言蚁剑和菜刀一样是一款优秀的webshell管理工具(shll控制端),与菜刀相比,蚁剑具有开源,自定义能力强,跨平台等优点。在waf普遍的今天,蚁剑这款工具提供了自定义header,自定义body,自定义编码器和解码器等功能来bypas
  目录