ThinkPHP v6.0.x 反序列化漏洞利用

前言:

上次做了成信大的安询杯第二届CTF比赛,遇到一个tp6的题,给了源码,目的是让通过pop链审计出反序列化漏洞。

这里总结一下tp6的反序列化漏洞的利用。

0x01环境搭建

现在tp新版本的官网不开源了,但是可以用composer构建环境,系统需先安装composer。然后执行命令:

composer create-project topthink/think=6.0.x-dev v6.0
cd v6.0
php think run

或者可以去github上下载,但是需要改动很多,也可以去csdn上下载人家配好了的源码。

tp6需要php7.1及以上的环境才能搭建成功。

搭建完成后访问ip加/public即可

0x02利用条件

需要在根目录下的 /app/controller的index.php里面存在unserialize()函数且为可控点,例如存在

unserialize($_GET['payload'])

0x03POP链分析

总的目的是跟踪寻找可以触发__toString()魔术方法的点

先从起点__destruct()或__wakeup方法开始,因为它们就是unserialize的触发点。

刚装了系统,还没下载phpstorm,先利用seay审计,然后全局搜索__destruct()方法,这里用了/vendor/topthink/think-orm/src下的Model.php,因为它里面含有save()方法可以被触发。

 

 

 跟进去,当$this->lazySave == true时,$this->save()方法将被触发

$this->lazySave == true时,会触发$this->save()方法

 

 

 跟进save()方法

 

 

发现对$this->exists属性进行判断,如果为true则调用updateData()方法,false则调用insertData()方法。

 试着跟进一下updateData()方法,发现updateData方法可以继续利用

 

 

 那么我们首先要构造参数使得updateData()被触发,需要将下面这个if过掉

 

 

 跟进isEmpty()方法看一下,

 

 

 保证isEmpty返回false,以及$this->trigger(‘BeforeWrite’)返回true即可绕过判断然后触发我们的updateData()方法。

构造$this->data为非空数组
构造$this->withEvent为false
构造$this->exists为true

继续跟进updateData()方法

需要绕过含有return的判断,第一个判断前面的save方法符合条件,第二个if需要$data非空

 

 

 $data的属性值由方法getChangedData()控制,跟进它

 

 

 如图两个变量初始值为[] 符合下面if的判断,结果返回1,即默认返回$data=1,确保这一步绕过之后我们就到了checkAllowFields()方法了

跟进checkAllowFields方法

发现了个字符串拼接

 

 

 由于$this->field==null,$this->schema==null,因此默认满足

那么我们看上面的db()方法

跟进db()方法

 

 

 发现满足判断条件的话,就存在$this->table . $this->suffix参数的拼接,这不就是那两个熟悉的拼接吗,可以触发__toString

满足connect函数的调用即可。

后面就是延续tp5反序列化的触发toString魔术方法了,就是原来vendor/topthink/think-orm/src/model/concern/Conversion.php的__toString开始的利用链

目前的逻辑链图:

 

 

 

 

 

 

 

 

 

 也就是:

__destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . $this->suffix(字符串拼接)——>toString()

这一部分构造的参数为:

$this->exists = true;
$this->$lazySave = true;
$this->$withEvent = false;

寻找__toString触发点

全局搜索__toString(),找到vendor/topthink/think-orm/src/model/concern/Conversion.php

 

 跟进toJson()方法

 

 跟进toArray()方法

 

 第三个foreach里面存在getAttr方法,他是个关键方法,我们需要触发他

触发条件:

$this->visible[$key]存在,即$this->visible存在键名为$key的键,而$key则来源于$data的键名,$data则来源于$this->data,也就是说$this->data和$this->visible要有相同的键名$key

构造参数,把$key做为参数传入getAttr方法

跟进getAttrr()方法

 

 然后$key值就传入到了getData()方法,跟进getData方法

 

 第一个if判断传入的值,$key值不为空,因此绕过,然后$key值传入到了getRealFieldName()方法,跟进getRealFieldName方法

 当$this->stricttrue时直接返回$name,即$key

回到getData方法,此时$fieldName = $key,进入判断语句:

 

 返回$this->data[$fielName]也就是$this->data[$key],记为$value,再回到getAttr

 

 也就是返回  $this->getValue($key, $value, null);

跟进getValue()方法

 

只要满足$this->withAttr[$key]存在且$this->withAttr[$key]不为数组就可以触发命令执行。

 最终会把$this->withAttr[$key]作为函数名动态执行函数,而$value作为参数,就可以利用执行系统函数达到命令执行。

到这里呈现了一条完整的POP链。

后半部分__toString的逻辑链图

 

 

 

 

 

 

 

 

 

 这一部分参数赋值:

$this->table = new think\model\Pivot();
$this->data = ["key"=>$command];
$this->visible = ["key"=>1];
$this->withAttr = ["key"=>$function];
$this->$strict = true;

因为这里的Model类为抽象abstract类,所以我们需要找一个继承于Model的类,比如Pivot

0x04 利用exp

tp默认主页目录为/public/,这下面的index.php会定位到/app/controller/index.php文件,因此url中/public/将调用app/controller/index.php,那么我们要知道里面的GET变量,用来传参数。

那么我们定位到app/controller/index.php,找到里面的unserialize()函数里面的GET变量。我这里的环境里面没有unserialize()函数,我们手动加上并加上一个GET变量

 

 网上我看到其他tp6框架的这个文件里面用的是$u这个变量来装unserialize()的值,并且用的GET变量是c,而且还对GET变量进行了base64_decode()编码,我这里为了对应安询杯比赛环境,没有加上base64编码。

首先利用phpggc工具生成exp,phpggc是一个反序列化payload生成工具,大佬们已经将tp6的exp上传到了phpggc,需要安装在linux上,然后执行以下命令生成exp的payload

./phpggc -u ThinkPHP/RCE2 'phpinfo();'

将生成的序列化字符串的payload传参给GET变量c,发送请求,然后将执行phpinfo()。

 

 如果是真实的tp6框架,试试将payload进行base64编码后再发送请求。

 

原文地址:https://www.cnblogs.com/-chenxs/p/12020777.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


(1)创建数据表: CREATE TABLE IF NOT EXISTS `think_form` (   `id` smallint(4) unsigned NOT NULL AUTO_INCREMENT,
组合查询的主体还是采用数组方式查询,只是加入了一些特殊的查询支持,包括字符串模式查询(_string)、复合查询(_complex)、请求字符串查询(_query),混合查询中的特殊查询每次查询只能定义一个,由于采用数组的
(1)创建模版:/App/Home/View/Form/edit.html   <FORM method=\"post\" action=\"__URL__/update\">
自定义配置文件user.php: <?php return array(    \'sex\'=>\'man\', ); config.php: <?php return array(
在一些成熟的CMS系统中,后台一般都包含一个配置中心(如织梦后台中系统设置),以方便站长在后台修改配置文件;那么这个功能是如果实现的呢?在ThinkPHP中有没有捷径可走呢?答案肯定是有的。下面大概说一下这个功能
废话不多说先上图预览下,即本博客的分页; 这个分页类是在thinkphp框架内置的分页类的基础上修改而来,原分页类的一些设计,在实际运用中感觉不是很方便;
在php中截取字符串的函数有很多,而在thinkphp中也可以直接使用php的函数,本文给大家简单的介绍thinkPHP模板中截取字符串的具体用法,希望能对各位有所帮助。
thinkphp开发图片上传,图片异步上传是目前比较方便的功能,这里我就不写css文件了,将代码写出来。
配置数据库:/app/Common/Conf/config.php 方法一: // 添加数据库配置信息 \'DB_TYPE\'   => \'mysql\',// 数据库类型
/app/Home/Controller/IndexController.class.php
(1)创建数据表: CREATE TABLE IF NOT EXISTS `think_data` (   `id` int(8) unsigned NOT NULL AUTO_INCREMENT,
(1)控制器设置:/app/Home/Controller/IndexController.class.php <?php namespace HomeController; use ThinkController;
(1)普通模式 http://localhost/index.php?m=module&a=action&var=value m参数表示模块,a操作表示操作(模块和操作的URL参数名称是可以配置的),后面的表示其他GET参数。
入库的时候用htmlspecialchars()处理含有html代码的内容 输出的时候用htmlspecialchars_decode()处理含有html代码的内容
<?php define(\'APP_NAME\',\'app\'); define(\'APP_PATH\',\'./app/\'); define(\'APP_DEBUG\',TRUE); // 开启调试模式
(1)创建控制器中定义read方法:/App/Home/Controller/FormController.class.php public function read($id=0){
一、实现不同字段相同的查询条件 $User = M(\"User\"); // 实例化User对象 $map[\'name|title\'] = \'thinkphp\';
如果你的数据完全是内部操作写入而不是通过表单的话(也就是说可以充分信任数据的安全),那么可以直接使用add方法,如:
查询表达式的使用格式: $map[\'字段名\'] = array(\'表达式\',\'查询条件\'); 表达式不分大小写,支持的查询表达式有下面几种,分别表示的含义是:
一、使用字符串作为查询条件 $User = M(\"User\"); // 实例化User对象 $User->where(\'type=1 AND status=1\')->select();