sky's blog

Laravel 5 Deserialization Chain Summary

字数统计: 1,413阅读时长: 6 min
2020/07/22 Share

前言

Laravel 7中由于一些有所类修复,导致一些pop chain无法使用,于是这次在Laravel 5系列中,也做一次总结,列举比较适合的切入点和查找新链的思路。

遍地撒网

为了更好的找出切入点,我这里直接写了一个脚本,列举出所有包含destruct的class和其destruct的定义,并将laravel 5和laravel 7进行比对:

其实不难发现,Laravel 7和Laravel 5在切入点这一块,并无太多的区别,几乎一致,一般修改均为一些微调。
同时我们可以搜寻一下切入点,一般分为如下几类:

1
2
3
__destruct中$this->xxxx()调用形式
__destruct中$this->xxx->yyy()调用形式
__destruct中built-in function调用形式

那么本文对于laravel 5的pop chain寻找也围绕这3点进行展开。

$this->xxxx()调用形式

根据这个调用形式进行寻找,有比较知名的CVE-2019-9081,我们可以看到其函数定义:

1
2
3
4
5
6
7
8
Illuminate\Foundation\Testing\PendingCommand::__destruct
public function __destruct()
{
if ($this->hasExecuted) {
return;
}
$this->run();
}

此处run函数可以引入RCE风险,此处分析不再赘述,可以参考文章:

1
https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/

当然这个类在Laravel 7中已经被修复。
除此之外,还有包括上篇文章我们分析过的:

1
2
3
4
5
6
7
Illuminate\Routing\PendingResourceRegistration::__destruct
public function __destruct()
{
if (! $this->registered) {
$this->register();
}
}

此处register函数可以引入RCE风险,也不再赘述,可以参考上一篇文章。
类似的调用情况同样很多,我简单列举几个:
GuzzleHttp\Cookie中常见的:

1
$this->save();

Monolog\Handler和Symfony\Component中常见的:

1
$this->close();

League\Flysystem中常见的:

1
$this->disconnect()

除此之外,还有一些在__destruct中出现频率不高的,如果感兴趣的都可以跟进进行尝试构造。

$this->xxx->yyy()调用形式

而对于这种调用形式,我们在之前的文章中提到过,其有2种思路进行利用:

1
2
__call魔法方法
同名函数

我们看几个典型的例子:

1
2
3
4
5
Illuminate\Broadcasting\PendingBroadcast::__destruct
public function __destruct()
{
$this->events->dispatch($this->event);
}

此处由于$this->events和$this->event均可控,因此可利用同名函数或__call的方式进行RCE pop chain的构造。
除此之外:

1
2
3
4
5
Symfony\Component\Routing\Loader\Configurator\ImportConfigurator::__destruct
public function __destruct()
{
$this->parent->addCollection($this->route);
}

同样有着相似的问题,虽然可能没有同名危险函数,但可以利用__call来进行构造,配合Faker\Generator来构造RCE pop chain。
并且如下类也存在类似的问题:

1
2
3
4
5
6
7
8
Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator::__destruct
public function __destruct()
{
if (null === $this->prefixes) {
$this->collection->addPrefix($this->route->getPath());
}
$this->parent->addCollection($this->collection);
}

相应的,其实我们在构造同名函数RCE pop chain的时候其实还算好,但当构造__call的时候,由于call name一般不可控,毕竟Faker\Generator中name可通过数组控制的情况不算特别多,那么此时可能会遇到瓶颈。
所以这种形式的利用手段并不是想象中那么丝滑(,还是需要精心构造的。

built-in函数

此类情况一般偏少,我们将搜寻锁定在敏感函数上,例如:

1
call_user_func、call_user_func_array、system、eval......

这里不难直接发现一个类:

1
2
3
4
5
6
7
GuzzleHttp\Psr7\FnStream::__destruct
public function __destruct()
{
if (isset($this->_fn_close)) {
call_user_func($this->_fn_close);
}
}

我们发现其直接调用了call_user_func,同时参数可控,为 $this->_fn_close,但难点在于该函数只可控第一个参数,因此这里我们可以想到能否调用类内方法,如果该方法不需传递参数且方法内敏感函数参数可控,为类内属性,那么即可利用。
这里不难想到,诸如:Illuminate\Foundation\Testing\PendingCommand的run方法,Illuminate\Routing\PendingResourceRegistration的register方法,都是可以通过其进行利用的。
当然这会显得有些取巧,如果你有兴趣的话,可以过一遍危险函数所在的方法,看看是不是其可以无参调用~
但是不幸的是,当前这个例子中,我们跟进类进行分析:

1
2
3
4
public function __wakeup()
{
throw new \LogicException('FnStream should never be unserialized');
}

由于存在wakeup,我们在利用这个chain的时候会抛出’FnStream should never be unserialized’的错误,而导致无法利用。
当然,我们也可以不仅仅找
destruct函数内的危险函数,尝试搜寻一些危险函数所在的方法和类,不难找到如下几个情况:PHPUnit\Framework\MockObject\Stub\ReturnCallback::invoke,关键代码如下:

1
2
3
4
public function invoke(Invocation $invocation)
{
return \call_user_func_array($this->callback, $invocation->getParameters());
}

又如Mockery\Loader\EvalLoader::load,关键代码如下:

1
2
3
4
5
6
7
public function load(MockDefinition $definition)
{
if (class_exists($definition->getClassName(), false)) {
return;
}
eval("?>" . $definition->getCode());
}

诸如此类情况,我们都可以将其整合进call_user_func或者call_user_func_array可控2个参数的地方,例如和Illuminate\Broadcasting\PendingBroadcast::__destruct组合,构造新的chain。

后记

Laravel 5由于过滤相对于Laravel 7来说缺失了一些,因此更容易被组建pop chain,同时laravel由于提供了大量的可用于构造的模块,也会衍生出各种排列组合的pop chain,但万变不离其中,最关键的还是寻找切入点。
本文提出的一些寻找pop chain的思路也是抛砖引玉,实际上寻找切入点的方式远远不止__destruct和文中所提及的3种类型,如果你有好的想法也欢迎和我联系交流~
总之还是那句话,求求CTF里别再出laravel的pop chain构造了( .

点击赞赏二维码,您的支持将鼓励我继续创作!
CATALOG
  1. 1. 前言
  2. 2. 遍地撒网
  3. 3. $this->xxxx()调用形式
  4. 4. $this->xxx->yyy()调用形式
  5. 5. built-in函数
  6. 6. 后记