0%

反序列化笔记总结

web反序列化

serialize* - *unserialize

序列化的基本操作

首先序列化的意义在于利于储存和传递PHP的值,序列化之后的格式如下:

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
<?php

class Kengwang
{
public $name = "kengwang";
public $age = 18;
public $sex = true;
public $route = LearningRoute::Web;
public $tag = array("dino", "cdut", "chengdu");
public $girlFriend = null;
private $pants = "red"; // not true
}

enum LearningRoute {
case Web;
case Pwn;
case Misc;
}

$kw = new Kengwang();
print_r(serialize($kw));

// 序列化之后
O:8:"Kengwang":7:{ // 定义了一个对象 [O], 对象名称长度为 [8], 对象类型数为 [7]
s:4:"name";s:8:"kengwang"; // 第一个字段名称是[4]个长度的"name", 值为长度为[8]的字符串([s]) "kengwang"
s:3:"age";i:18; // 第二个字段名称是长度为[3]的"age", 值为整数型([i]): 18
s:3:"sex";b:1; // 第三个字段名称是长度为[3]的"sex", 值为布尔型([b]): 1 -> true
s:5:"route";E:17:"LearningRoute:Web"; // 第四个字段名称是长度为[5]的"route", 值为枚举类型([E]), 枚举值长度为 [17], 值为 "...":
s:3:"tag";a:3:{ // 长度为 [3] 的数组([a])
i:0;s:4:"dino"; // 第[0]个元素
i:1;s:4:"cdut";
i:2;s:7:"chengdu";
}
s:10:"girlFriend";N; // 字段 "girlFriend" 为 NULL
s:15:" Kengwang pants";s:3:"red"; // 私有字段名称为 类型名 字段名, 其中类型名用 NULL 字符包裹
}

魔术方法

魔术方法

  1. __wakeup()方法
    在执行反序列化的时候会检查是否存在此方法,若有则先执行此方法
  2. __sleep()方法
    在执行序列化的时候会检查是否存在此方法,若有则限制性次方
  3. __construct()方法
    PHP 允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法

执行顺序

序列化时会先调用 sleep 再调用 destruct, 故而完整的调用顺序为: sleep -> (变量存在) -> destruct

反序列化时如果有 __wakeup 则会调用 __wakeUp 而不是 __construct, 故而逻辑为 __wakeUp/__construct -> (变量存在)

绕过

__wakeup()

当反序列化时, 给出的字段个数的数字小于提供的字段个数, 将不会执行 __wakeup

不完整类绕过序列化检测

-2025WHUCTF新生赛

当我们尝试反序列化到一个不存在的类是, PHP 会使用 __PHP_Incomplete_Class_Name 这个追加的字段来进行存储

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
// 1. 先序列化一个对象(备份)
class Cat {
public $name = "咪酱";
}
$cat = new Cat();
$str = serialize($cat);
// 序列化结果:O:3:"Cat":1:{s:4:"name";s:4:"咪酱";}
// 这里面明明白白写了类名是"Cat"


// 2. 现在删除Cat类(模拟新环境没有这个类)
unset($GLOBALS['Cat']);


// 3. 反序列化(恢复备份)
$obj = unserialize($str);


// 4. 看看这个对象是什么样的
echo get_class($obj); // 输出:__PHP_Incomplete_Class(临时类型)

// 把对象转成数组看内部数据
print_r((array)$obj);
// 输出:
// Array (
// [__PHP_Incomplete_Class_Name] => Cat // 这里就是那个“特殊标签”,记着原来的类名
// [name] => 咪酱 // 原来的属性数据还在
// )

简单来讲就是下面四种情况:

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
/**
* PHP __PHP_Incomplete_Class
* 用注释标注每个步骤的输入→变化→输出,类和对象用结构描述,序列化字符串用标准格式
*/

// ==================================== 场景1:反序列化一个不存在的类(无__PHP_Incomplete_Class)
/*
输入:序列化字符串(类User不存在)
O:4:"User":2:{s:4:"name";s:5:"张三";s:3:"age";i:20;}

变化过程:
1. 反序列化时,PHP找不到User类
2. 自动创建__PHP_Incomplete_Class类型的临时对象
3. 给临时对象添加__PHP_Incomplete_Class_Name属性,值为"User"(原始类名)
4. 保留User类的所有原始属性(name、age)

输出:__PHP_Incomplete_Class对象
结构:{
__PHP_Incomplete_Class_Name: "User", // 自动添加的标签属性
name: "张三", // 原始属性
age: 20 // 原始属性
}
*/


// ==================================== 场景2:序列化一个__PHP_Incomplete_Class对象
/*
输入:场景1输出的临时对象
{
__PHP_Incomplete_Class_Name: "User",
name: "张三",
age: 20
}

变化过程:
1. 序列化时,PHP识别到是__PHP_Incomplete_Class对象
2. 读取__PHP_Incomplete_Class_Name的值"User",作为新的类名
3. 删除__PHP_Incomplete_Class_Name属性(不再需要)
4. 属性总数从3减为2(剔除标签属性)
5. 保留其他原始属性(name、age)

输出:序列化字符串(恢复为原始类名,无标签)
O:4:"User":2:{s:4:"name";s:5:"张三";s:3:"age";i:20;}
*/


// ==================================== 场景3:直接序列化不存在的类(创建对象)
/*
输入:尝试实例化不存在的类NoClass
new NoClass();

变化过程:
1. PHP无法实例化未定义的类NoClass
2. 直接抛出错误,不会生成对象
3. 无序列化过程(没有对象可序列化)

输出:错误信息
Fatal error: Uncaught Error: Class "NoClass" not found
*/


// ==================================== 场景4:反序列化含__PHP_Incomplete_Class类名的字符串(标签类存在)
/*
前提:已定义类Student(结构:{name: string, age: int})

输入:序列化字符串(类名是__PHP_Incomplete_Class,含标签属性)
O:22:"__PHP_Incomplete_Class":3:{
s:27:"__PHP_Incomplete_Class_Name";s:7:"Student"; // 标签属性(原始类名)
s:4:"name";s:5:"李四";
s:3:"age";i:22;
}

变化过程:
1. 反序列化时,PHP读取类名是__PHP_Incomplete_Class
2. 检查到__PHP_Incomplete_Class_Name属性,值为"Student"
3. 发现Student类已存在,直接创建Student类对象
4. 将原始属性(name、age)赋值给Student对象

输出:Student类正常对象
结构:Student{
name: "李四",
age: 22
}
*/


// ==================================== 场景4变种:反序列化含__PHP_Incomplete_Class类名的字符串(标签类不存在)
/*
输入:序列化字符串(类名是__PHP_Incomplete_Class,标签类Teacher不存在)
O:22:"__PHP_Incomplete_Class":3:{
s:27:"__PHP_Incomplete_Class_Name";s:7:"Teacher"; // 标签属性(原始类名,不存在)
s:4:"name";s:5:"王五";
s:3:"age";i:30;
}

变化过程:
1. 反序列化时,PHP读取类名是__PHP_Incomplete_Class
2. 检查到__PHP_Incomplete_Class_Name属性,值为"Teacher"
3. 发现Teacher类不存在,创建__PHP_Incomplete_Class临时对象
4. 保留标签属性和原始属性

输出:__PHP_Incomplete_Class临时对象
结构:{
__PHP_Incomplete_Class_Name: "Teacher",
name: "王五",
age: 30
}
*/


// ==================================== 核心逻辑总结(笔记重点)
/*
1. 反序列化不存在的类 → 生成带标签的临时对象(保留原始属性)
2. 序列化临时对象 → 自动恢复为原始类名的字符串(删除标签,属性数-1)
3. 直接实例化不存在的类 → 报错,无法序列化
4. 反序列化含__PHP_Incomplete_Class的字符串:
- 标签类存在 → 恢复为该类的正常对象
- 标签类不存在 → 再次生成临时对象
*/
?>

原生类

php本身存在的类,有些类会存在可调用的方法例如:Error、Exception等


参考网站博客:

-------------到底咯QAQ嘎嘎-------------