最新Laravel(8.x)反序列化漏洞
4ut15m 漏洞分析 8824浏览 · 2021-12-14 15:13

Laravel v8.x反序列化漏洞

环境搭建

使用composer一键安装最新版Laravel

composer create-project --prefer-dist laravel/laravel laravel

在app\Http\Controllers中添加Test控制器,加入反序列化点,如下图。

在routes\web.php中添加以下路由。

注释掉app\Kernel.php中关于CSRF验证的部分(第38行)。

当访问首页与/hello正常时,环境搭建完毕。

漏洞分析

反序列化利用链如下。

Illuminate\Testing\PendingCommand->__destruct()
Illuminate\Testing\PendingCommand->run()
Illuminate\Container\Container->make()
Illuminate\Container\Container->resolve()

先看命令执行的点,Illuminate\Container\Container->resolve(),当$extender$object可控时,可以进行代码执行。

protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        ···略···

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

       ···略···
        return $object;
    }
/**
* php system函数原型
* system(string $command, int &$return_var = ?): string
*/

再看链首Illuminate\Testing\PendingCommand->__destruct()

hasExecuted不为真时,进入Illuminate\Testing\PendingCommand->run(),关键部分代码如下。

跟进make方法,可以看到实现该方法的子类有两个。

其中包含了Illuminate\Container\Container,又由于$this->app可控,故在此可跟进到Illuminate\Container\Container->make()

在第一次进入到Illuminate\Container\Container->make()方法时,$abstract不可控,并且为Kernel::class,漏洞方法resolve()出现,继续跟进Illuminate\Container\Container->resolve()

protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);
        if ($raiseEvents) {
            $this->fireBeforeResolvingCallbacks($abstract, $parameters);
        }

        $concrete = $this->getContextualConcrete($abstract);
        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

为使后面讲述的时候不太绕,我们先讲$abstract = $this->getAlias($abstract),跟进到$this->getAlias()

$this->aliases可控,当设置了$this->aliases[$abstract]变量时,会再调用一次该方法,当未设置该变量时则直接返回$abstract的值。

前面我们说到,第一次调用make()时,传入的$abstract不可控,并且为Kernel::class,故我们可以通过设置Illuminate\Container\Container->aliases[Kernel::class]=>"可控值"的方式来控制$this->getAlias($abstract)

所以,在经过了$abstract = $this->getAlias($abstract)处理之后,$abstract便是可控的了。

$abstract=$this->aliases[Kernel::class]

现在,只要控制了$extender$object便能rce,$extender来自$this->getExtenders($abstract),跟进Illuminate\Container\Container->getExtenders()

因为$this->extenders可控,$this->getAlias($abstract)可控,所以$extender可控。

下面看$object变量的获取部分。

if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

两个方式都可以获得$object,分别跟进$this->build()$this->make()后,不太想看$this->build(),故分析第二个方式能否返回可控值。

这是第二次调用make()并且传入的值为$concrete,再次跟进resolve(),这次我们着重看resolve()return的地方。

第一次return的部分如下。

再看看$needsContextualBuild如何取得。

这里的$parameters是在调用make()时传入的,默认为空数组,此时我们能够想到,倘若这里的$abstract也即是传入的$concrete可控,我们就能控制第二次调用make()的返回值,也即是控制$object

回看$concrete是如何取得的,同样是两个地方。

$concrete = $this->getContextualConcrete($abstract);
                ···略···
if (is_null($concrete)) {
     $concrete = $this->getConcrete($abstract);
}

先跟进$this->getConcrete()

可以知道,当这里传入的$abstract可控时,$concrete就可控了。而这里的$abstract是进入resolve()方法进行一次处理后得到的,即$this->aliases[Kernel::class]

至此,我们知道$concrete可控->$this->make($concrete)返回值可控->$object可控。

所以,此时我们只要保证if ($this->isBuildable($concrete, $abstract))false即可。

跟进$this->isBuildable()

$abstract可控,这里的$concrete是经过$concrete = $this->getContextualConcrete($abstract)处理的,只要我们未做特殊的配置(也即是通过修改各个属性去改变$this->getContextualConcrete()的返回值),那么它处理后的$concrete=null

最终isBuildable的返回值就为false

至此,我们先做一个小结。

在Illuminate\Testing\PendingCommand类中,需要做如下设置:
$this->hasExecuted = false;
$this->app = Illuminate\Container\Container对象;

在Illuminate\Container\Container类中,需要做如下设置:
$this->aliases = array(Kernel::class=>"4ny0ne");
$this->bindings = array("4ny0ne"=>array("concrete"=>"4ut15m"));
$this->instances = array("4ut15m"=>"命令");
$this->extenders = array("4nyone"=>"system");

在简单编写EXP之后可以发现,提交payload之后会出现一个异常,而这个异常,则是在Illuminate\Testing\PendingCommand->mockConsoleOutput()中抛出的。

调试跟进,发现异常抛出的位置。

所以,我们需要使得Illuminate\Testing\PendingCommand->text为一个拥有expectedOutput属性的对象,全局搜索expectedOutput,在一个trait类InteractsWithConsole中找到该属性。

全局搜索使用了该trait的类,找到一个接口TestCase

最后找到该接口的一个实现类ExampleTest,修改最终EXP,执行命令如下。

EXP

anyserial

0 条评论
某人
表情
可输入 255