thinkphp sql注入分析


0x01

漏洞概要

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

漏洞影响版本

5.0.13<=ThinkPHP<=5.0.155.1.0<=ThinkPHP<=5.1.5

环境搭建

环境version

我在使用PHP7以上的高版本搭建时失败了,改用PHP5.4.45

Mysql5.7.32

获取thinkPHP

composer create-project --prefer-dist topthink/think=5.0.15 tpdemo

我们需要修改composer.json文件的require内容如下:

"require": {
    "php": ">=5.4.0",
    "topthink/framework": "5.0.15"
},

并且执行

composer update“

mysql环境

执行

create database tpdemo;
use tpdemo;
create table users(
    id int primary key auto_increment,
    username varchar(50) not null
);

修改php文件

修改入口文件***/tpdemo/application/index/controller/Index.php**内容为

get('username/a');
        db('users')->insert(['username' => $username]);
        return 'Update success';
    }
}

修改application/database.php文件内容,连接数据库;

并开启 application/config.php 中的 app_debugapp_trace

漏洞复现

Payload:

http://localhost:8888/tpdemo/public/index.php?username[0]=dec&username[1]=updatexml(1,concat(0x7,database(),0x7e),1)&username[2]=1“

在图中位置添加断点

img

进入insert方法,步过到

$sql = $this->builder->insert($data, $options, $replace);

insert方法所在的Mysql 类继承于 Builder 类,即上面的 $this->builder->insert() 最终调用的是 Builder 类的 insert 方法。我们步入到Builder 类的insert方法里:

可以看到我们输入的username参数作为数组被储存在$data中,并且使用parseData方法进行处理,而 parseData 方法直接将来自用户的数据 $val 进行了拼接返回。我们的恶意数据存储在 $val[1] 中,虽经过了 parseKey 方法处理,但丝毫不受影响,因为该方法只是用来解析处理数据的,并不是清洗数据

img

我们步入parseData进行查看:

img

因为在我们传入的恶意payload中,**$val数组的第一个元素是inc,所以在图中switch语句中会匹配到箭头所指的位置,使用parseKey方法处理$val数组的第二个元素,也就是我们的恶意payload,返回给$result[$item]**,然后再回到 Builder 类的 insert 方法,直接通过替换字符串的方式,将 $data 填充到 SQL 语句中,进而执行,造成 SQL注入漏洞

img

此时的**$sql内容为“INSERT INTO users (username) VALUES (updatexml(1,concat(0x7,database(),0x7e),1)+1) “**

成功造成sql注入:

img

思考

我们看到在parseData方法中,switch结构去匹配**$val数组的第一个元素,值为incdec的处理行为相似,所以我们传入的username数组第一个元素值也可以写成dec**

至于为什么exp不能使用,实际上, exp 的情况早在传入 insert 方法前就被 ThinkPHP 内置过滤方法给处理了,如果数据中存在 exp ,则会被替换成 exp空格 ,这也是为什么 ThinkPHP 官方没有对 exp 的情况进行处理的原因了。具体内置过滤方法的代码如下:

路径:*/tpdemo/thinkphp/library/think/Request.php

img

如果我们将EXP从这个正则中删除,username第一个元素传入exp也可以造成sql注入


0x02

漏洞概要

本次漏洞存在于 Mysql 类的 parseArrayData 方法中由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生。漏洞影响版本: 5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)。

搭建环境

获取thinkPHP

composer create-project topthink/think=5.1.* tp5

composer.json 文件的 require 字段设置成如下:

"require": {
    "php": ">=5.6.0",
    "topthink/framework": "5.1.7"
}

执行composer update

创建数据库

create database tpdemo;
use tpdemo;
create table users(
    id int primary key auto_increment,
    username varchar(50) not null
);
insert into users(id,username) values(1,'m1saka');

修改配置文件

config/database.php 文件中配置数据库相关信息,并开启 config/app.php 中的 app_debugapp_traceimg

img

修改入口文件

application/index/controller/Index.php 文件代码设置如下:

get('username/a');
        db('users')->where(['id' => 1])->update(['username' => $username]);
        return 'Update success';
    }
}

复现过程

Payload:

http://localhost:8888/tpdemo/public/index.php?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0

在图中所示位置下断点:

img

浏览器输入payload,我们步入到Query.php下的update方法, payload 数据经过 ThinkPHP 内置方法的过滤后(不影响我们的 payload ),直接进入了 Query 类的 update 方法。该方法调用了 Connection 类的 update 方法,该方法又调用了 $this->builderinsert 方法,这里的 $this->builder\think\db\builder\Mysql 类,该类继承于 Builder 类。

步入Connection.php下的update方法

img

可以看到update方法中调用了parseData方法,我们继续跟进parseData方法,此时我们传入的username数组参数作为parseData方法的**$data参数,又赋值给$val参数,进入下面的switch case**语句进行处理。

img

会进入default中使用parseArrayData处理,我们步入到parseArrayData中:

img

$data是我们传入的参数,所以在此方法中,**$fun $point** 和**$value都是我们可以控制的,$result** 相当于 $a(‘$b($c)’) 其中 $a、$b、$c 均可控。最后形成的 SQL 语句如下:

UPDATE `users`  SET `username` = a('b($c)')  WHERE  `id` = 1;

接着我们想办法闭合即可。我们令 $a = updatexml(1,concat(0x7,user(),0x7e),1)^$b = 0$c = 1 ,即:

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

这样就可以造成sql注入。

img

贴一张mochazz师傅总结的图

img


文章作者: Wh1teR0be
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Wh1teR0be !
  目录