Thinkphp3全漏洞分析
2023-03-29 23:03:03
451
{{single.collect_count}}

这里给大家推荐两个phpdebug的docker容器,也是我一直在用的,不用配置很复杂的环境,即开即用:

在这里插入图片描述

框架介绍

基本信息
ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,遵循Apache 2开源协议发布,使用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库)、RoR的ORM映射和ActiveRecord模式。ThinkPHP可以支持windows/Unix/Linux等服务器环境,正式版需要PHP 5.0以上版本,支持MySql、PgSQL、Sqlite多种数据库以及PDO扩展。

Thinkphp 2.x、3.0-3.1版代码执行漏洞分析

此漏洞为tp框架中Lite精简模式中存在可能的漏洞,ThinkPHP 开启lite模式后,会加载Lite下的Dispacher.class.php文件去匹配URL并分发用户请求, 而在该文件中的一处使用了perg_replace函数的 /e参数,

在这里插入图片描述
此函数/e模式会将其的第二个参数当作php代码执行,而在此文件中一二参数都为可控,\1与\2分别代表着第一个参数中的两个括号的位置,例如当我们传访问的url为index.php?s=1/2/3/4/5/6时,在第一次进行匹配是\1即代表1,\2即代表2,在第二次匹配时\1即代表3,\2即代表4。
本地复现:
在这里插入图片描述
payload:
?s=/1/2/3/${eval($_GET[1])}&1=system(%22whoami%22);

要将我们的payload写到偶数位也就是\2处,原因是\2处为函数的第二个参数处且被双引号包裹,可以解析变量

thinkphp3.2.3 SQL注入漏洞-where分析

记得安装mysql_pdo.so。不然连接数据库会报错的

tp3中的常用方法:

A快速实例化Action类库B执行行为类C配置参数存取方法D快速实例化Model类库F快速简单文本数据存取方法L 语言参数存取方法M快速高性能实例化模型R快速远程调用Action类方法S快速缓存存取方法U URL动态生成和重定向方法W 快速Widget输出方法D函数实例化的是你当前项目的Lib/Model下面的模块。如果该模块不存在的话,直接返回实例化Model的对象(意义就与M()函数相同)。而M只返回,实例化Model的对象。它的$name参数作为数据库的表名来处理对数据库的操作。I方法是ThinkPHP众多单字母函数中的新成员,其命名来自于英文Input(输入),主要用于更加方便和安全的获取系统输入变量,可以用于任何地方D方法和M方法,这两个方法的区别在于M方法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模型类,则会自动调用M方法。通俗一点说:M实例化参数是数据库的表名。D实例化的是你自己在Model文件夹下面建立的模型文件

写个控制器:

<?phpnamespace Home\Controller;use Think\Controller;class IndexController extends Controller {public function index(){$Dao=M('users');$result=$Dao->find(1);if ($result){echo"connect success";}var_dump($Dao->find(I('GET.id')));}}

访问下,我们输入的是id=1’,但是还是对数据库进行了正常查询,我们先简单跟下:
在这里插入图片描述
在这里插入图片描述

进I方法里面看一眼,这个获取我们get传进去的值:

在这里插入图片描述
为我们挑选一个过滤器,因为我们一开始对I方法,并没有传入filter,所以我们这里filter的值是null,所以派一个默认的filter给我们:

在这里插入图片描述

C函数我没跟进去看,直接看的idea的返回结果,这里给的filter是:htmlspecialchars

在这里插入图片描述

但是这玩意貌似不过滤单引号:
在这里插入图片描述

在下面进行过滤:

在这里插入图片描述
在往下走,走到后面,会回调think_filter这个函数进行过滤:
在这里插入图片描述

瞅一眼过滤了啥,这正则对我们当前payload的作用几乎为。。。:
在这里插入图片描述

回到过去,最后返回的还是1’ ,没过滤掉根本:

在这里插入图片描述
接着进了find方法,这里给options赋值,赋成了二维数组:
在这里插入图片描述
我这里重新换了个pyload:1’and%201=1%23,打个断点直接到这:
在这里插入图片描述
接着往下走,会有些对id参数是否是数组的判断,我们这里都进不去。就往下跟我们发现在这下面这个地方,我们的1’and%201=1%23变成了1:
在这里插入图片描述
对这个函数打个断点,进去瞅下发现有个parseType函数:
在这里插入图片描述

跟进去看看,这个函数是判断了我们数据库里的这个字段的类型,然后进行相应的处理,我这里用的是id参数,是int型,所以他直接用intval做了个强转,我们的1’and%201=1%23就是在这里变成了1:
在这里插入图片描述
我们进数据库把id类型改成string,然后在断点定位到这,跳过了这个parseType函数,接着走到了查询的地方,跟进下:
在这里插入图片描述
发现了在buildSelectSql处单引号被转意:

在这里插入图片描述
打个断点跟进buildSelectSql处:
在这里插入图片描述

跟进parsesql函数:
在这里插入图片描述
跟进去where那个:

在这里插入图片描述
在跟进去这个解析相关的函数:
在这里插入图片描述
在跟下这个parsevalue:
在这里插入图片描述
第一个if通过了,这里执行了这个escapeString,跟进一下这个escapeString函数,可以看到就是在这里对我们的单引号进行了转意:
在这里插入图片描述

数字,字符都不行,只能尝试数组,看看在有些条件判断处能否进行一个绕过

payload1

我们传入的参数,是在_parseType()中被强转为int,如果我传?id[where]=1 and 1=1时根本不会进入这个if,更不用说进入强转的那个函数:

在这里插入图片描述
我们不传数组时的,到这的options中的where是个数组,所以会进到那个强转的函数:
在这里插入图片描述

再往下走,因为这回$where变成了字符串,所以在parseWhere这里直接拼接成了语句,依次返回执行:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
结果:
在这里插入图片描述

thinkphp3.2.3 SQL注入漏洞-exp注入分析

payload:?name[0]=exp&name[1]==1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)
控制器:

<?phpnamespace Home\Controller;use Think\Controller;class IndexController extends Controller {public function index(){$User = D('Users');$map = array('name' => $_GET['name']);$user = $User->where($map)->find();var_dump($user);}}

这里的写法可以类比一下where注入的那个控制器,where注入的传参方式是I(‘GET.id’),而这里是 $map = array(‘name’ => $_GET[‘name’])与where($map)结合,这个原因放到最后说,这可以打个断点,看看where函数主要做了什么:

在这里插入图片描述

主要在where函数里就做了个赋值操作,主要是赋值以后的find操作,我们跟进去看看,跟进以后的大多数过程都和之前比较类似,直到这里parseSql里的parseWhere方法:
在这里插入图片描述

跟进去parseWherer以后,主要是parseWhereItem方法,跟进去看看

在这里插入图片描述

首先会对$exp进行赋值,赋的值就是我们传入的 name[0]=exp
在这里插入图片描述
之后会进行一个判断,拼接这一步是比较重要的:
在这里插入图片描述
拼接以后会逐步递归返回,最后进行执行:
在这里插入图片描述
在这里插入图片描述

再说一下,这里为什么不能用I(‘GET.id’)进行传参,在我们调试where注入的时候,用I(‘GET.id’)进行传参时,会有一个think_filter进行过滤如下图,正则里写了"exp“:

在这里插入图片描述
结果:

在这里插入图片描述

thinkphp3.2.3 SQL注入漏洞-bind注入分析

控制器:

<?phpnamespace Home\Controller;use Think\Controller;class IndexController extends Controller {public function index(){$User = M("users");$user['id'] = I('id');$data['PWD'] = I('PWD');$valu = $User->where($user)->save($data);var_dump($valu);}}

payload : id[0]=bind&id[1]=0 and updatexml(1,concat(0x7e,user(),0x7e),1)&PWD=
这里的id[0]=bind可以参考exp注入中的name[0]=exp,主要是为了进到那个if判断:

在这里插入图片描述
而我们这里对比exp注入,为什么要加一个id[1]=0呢,我们先输入id[0]=bind&id[1]=abc&PWD= 试试:

在这里插入图片描述

主要进入的是update方法,并且bind注入会产生绑定参数。就是假如我们输入的payload是id[0]=bind&id[1]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)&PWD=,那么最后没进行替换之前编译出的语句是:UPDATE users SET PWD =:0 WHERE id = :1 and updatexml(1,concat(0x7e,user(),0x7e),1),如果payload是id[0]=bind&id[1]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)&PWD=那么最后没进行替换之前编译出的语句是:UPDATE users SET PWD =:0 WHERE id = :0 and updatexml(1,concat(0x7e,user(),0x7e),1).

这是在这里完成的:

在这里插入图片描述

在接着用id[0]=bind&id[1]=0 and updatexml(1,concat(0x7e,user(),0x7e),1)&PWD=这个payload进行bind注入调试,可以看到我们最终拼接的没有进行替换的语句是这样的:

UPDATE `users` SET `PWD`=:0 WHERE `id` = :0 and updatexml(1,concat(0x7e,user(),0x7e),1)

在这里插入图片描述
但是我们最终执行的时候,在execute函数里的strtr函数是会对:0进行参数替换的:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
上面红框圈出来的大概的意思是,对我们$this->bind中的数组的每个键的值先加上个单引号,然后再进行替换,可以看看strtr函数的作用:

在这里插入图片描述
这里的$this->bind是我们可以控制的,所以我们要我们的payload中构造一个有 “:0” => “xxxxx” 的这样的一个键值对,来达到对他的编译出的语句中的 :0进行替换的一个目的,我们要id[1]=0 and xxx就是为了要他先编译出的语句中含有:0不含有 :1、 :2、 :3 、:u 、:e 、:a这种其他字符,这就是我们的目的,在$this->bind中产生这样一个值的这步是在这里完成的,并且$val参数的值,就是我们PWD传入的值:

在这里插入图片描述

在这里插入图片描述
因为我这里的PWD输入的为空,所以经过bindParam后$this中是这样的结果:

在这里插入图片描述

最后在update方法中的execute进行先替换后执行:

替换:
在这里插入图片描述

执行:

在这里插入图片描述

回帖
全部回帖({{commentCount}})
{{item.user.nickname}} {{item.user.group_title}} {{item.friend_time}}
{{item.content}}
{{item.comment_content_show ? '取消' : '回复'}} 删除
回帖
{{reply.user.nickname}} {{reply.user.group_title}} {{reply.friend_time}}
{{reply.content}}
{{reply.comment_content_show ? '取消' : '回复'}} 删除
回帖
收起
没有更多啦~
{{commentLoading ? '加载中...' : '查看更多评论'}}