php反序列化魔术方法详解

php

php反序列化

php魔术方法总结

1. 类的构造函数 __construct()

php对象创建后首先被调用的函数。在每一个类中都有一个构造函数,可以自定义。如果没有显示声明它,则会默认存在一个没有参数并且内容为空的构造方法。

  • 触发:只有在new一个新的对象的时候会触发,在serialize 序列化和unserialize反序列化中都不会触发
1
2
3
public function __construct(){
//需要执行的操作,比如对参数初始化
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class demo1{
private $k1;
public function __construct()
{
echo("构造函数被调用"."<br>");
}
public function f1(){
echo("f1 函数被调用");
}
}
echo("0000"."<br>");
$f=new demo1();
echo("1111"."<br>");
$a=serialize($f);
echo("2222"."<br>");
unserialize($a);
?>

2. 析构函数 __destruct()

1
2
3
public function __destrcut(){
echo("触发")
}
  • 触发:__destruct()在对象被销毁和serialize反序列化的情况下会被触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class demo1{
private $k1;
public function __destruct()
{
echo("析构函数被调用"."<br>");
}
}
$f=new demo1();
echo("Time_0"."<br>");
$a=serialize($f);
echo("Time_1"."<br>");
unset($f);
echo("Time_2"."<br>");
unserialize($a);
?>

image-20250116190341455

3. __toString

  • 触发:当对象类被当做字符串处理时,会触发__destruct()

  • 一般做题目时,基本是__destruct()析构函数中有echo,打印字符串,链子触发__destruct()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
public function __toString()
{
echo("__toString 触发");
return "";
}
}
$f=new demo1();
echo($f);
?>

image-20250116190942031

4. __call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
//当调用不存在的方法时,方法名作为参数传到$name 变量,方法名的输入参数传到arguments参数列表中
public function __call($name, $arguments)
{
// TODO: Implement __call() method.
echo($name."---".$arguments[0]);
}
}
$f=new demo1();
$f->f2("123");//调用不存在的方法f2()
?>

image-20250116191453469

  • 比如楚慧杯决赛的这里
1
2
3
4
5
6
7
8
9
10
class song{
public $banana; //banana 可控,让他指向的方法ernb()不存在,触发__call
public $abble;
public function __toString()//对象当做字符串的时候,会触发__toString
{
if($this->abble == "abble"){
return $this->banana->ernb();//banana 可控,让他指向的方法ernb()不存在,触发__call
}
}
}

5. __get()

  • 触发:访问一个对象不存在的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class demo1{
private $test;
public function f1(){
echo("f1 函数被调用");
}
public function __get($name)//不存在的变量test会以参数传到$name
{
echo($name);
}
}
$f=new demo1();
$f->test1;//不存在的变量test
?>

image-20250116191801591

  • 在pop链题目中如下
1
2
3
4
5
6
7
class rap{
public $text;
public function __call($name, $arguments)//$test->bucunzai('123','456')调用一个不存在的方法时,会触发__call
{
return $this->text->aaabbb; //text 可控,让他指向的属性aaabbb不存在,触发__get
}
}

6. __set()

  • 触发:__set()当给一个对象不存在的变量赋值时触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
class test{
private $test1;
public function setTest1(){
echo "setTest1函数被调用<br>";
}
public function __set($name,$value){
echo($name."---".$value);
}
}
$foo = new test();
$foo->test=17;
?>

image-20250116200420508

7. isset()

  • 触发:当对不可访问属性调用isset()或empty()时会触发,例如访问类的私有属性,类不存在的成员属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
public function __isset($name)
{
echo($name);
}
}
$f=new demo1();
$f2=unserialize(serialize($f));//反序列化
isset($f2->k1);//使用isset方法判断私有成员属性k1
empty($f2->k1);//使用empty方法判断私有成员属性k1
?>

image-20250116200633462

8. unset()

触发:当尝试使用unset() 销毁函数去销毁一个不可访问的成员属性时会触发,不可访问(包括私有成员属性,不存在的成员属性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $XU17;
public function f1(){
echo("f1 函数被调用");
}
public function __unset($name)
{
echo($name);
}
}
$f=new demo1();
$f2=unserialize(serialize($f));//反序列化
unset($f2->XU17);//使用unset销毁私有成员属性k1
unset($f2->no_XU17);//使用unset销毁不存在的成员属性faaa
?>

image-20250116200934179

9. __sleep

  • 触发:当对象被serialize 序列化时触发调用__sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
public function __sleep()
{
echo("在被序列化时被调用");
}
}
$f=new demo1();
echo("00000"."</br>");
serialize($f);
?>

image-20250116201553222

10.__wakeup()

  • 触发:当进行unserialize 反序列化对象时,__wakeup魔术方法会被触发,看起来__wakeup__sleep触发条件是相反的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
public function __wakeup()
{
echo("在被反序列化时被调用");
}
}
$f=new demo1();
$uz=serialize($f);
echo("00000"."</br>");
unserialize($uz);
?>

image-20250116201808629

11. __invoke()

  • 触发:当一个对象类中存在__invoke魔术方法,这个对象类被当作函数进行调用时,就会触发__invoke魔术方法,而不会产生错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header("Content-Type:text/html;charset=utf-8");
highlight_file(__FILE__);
class demo1{
private $k1;
public function f1(){
echo("f1 函数被调用");
}
public function __invoke()
{
echo("__invoke 被触发了");
}
}
$f=new demo1();
$f();
?>

image-20250116201853179

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
//用户类

//属性和
class user{
public $username; //公有属性
private $password; //私有属性
protected $userType;
public static $platform = "xu17";

public static{

}

//公共方法
public login(){

}

public logout(){

}

}

类的定义,可以认为是设计图

实例化一个类,从设计图转换为一个可以用的变量,变量类型不再是字符串或者数字,而是一个对象

1
2
3
4
5
6
7
8
9
10
11
12
$u = new user();

$p = new user();//new 的时候 静态属性不变

$u->username;

$p->username;

//外部不可以引用 $this->private,类的内部可以
public login(){
$this->password;
}

类和对象的关系,类似于 设计图(蓝图) 与 生产出的产品 之间的关系

属性权限

  1. public 外部可以通过箭头访问到
  2. private 内部通过 $this->username 访问到
  3. protected 表示 自身及其子类 和父类 能够访问

类的继承

  • 三种用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class normalUser extends user{
public $score;

public function play(){
echo $this->userType;
}
}

$n = new normalUser();


class vipUser extends user{
public $score;


}

class adminUser extends user{
public $score;


}

类的属性修饰符

修饰:

静态属性 static

final属性

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
//用户类

//属性和
class user{
public $username; //公有属性
private $password; //私有属性
protected $userType;
public static $platform = "xu17";
public final $uuid = "0x36d";

public static{

}

//公共方法
public final function login(){
echo $this -> password;
}

public function logout(){

}

}
class normalUser extends user{
public $score;

public function play(){
echo $this->userType;
}
}

$n = new normalUser();

echo $n ->uuid;
}

类的继承

普通用户 vip用户 管理员用户

都属于user类

class normalUser extends user{
public $score;

public function play(){
   echo  $this->userType;
}

}

$n = new normalUser();

class vipUser extends user{
public $score;

}

class adminUser extends user{
public $score;

}

方法的属性修饰符

public
private
protected

修饰:
静态属性 static
final属性 final

类的分类

1 普通类 没有任何修饰

class user{

public function login(){

}

public function logout(){

}

}

2 抽象类

abstract class user{

public function login(){

}

public function logout(){

}

abstract function play();

}

类里面的方法,有些是有详细的实现,有些就只有个方法名字,没具体实现

抽象类 不能被 new 也就是不能被直接 实例化对象

3 接口 interface

为了实现多继承效果

extends user
implements 可以实现多个接口

4 trait

可以认为是代码段 ,方便复制粘贴

5 匿名类

//一次性匿名类的例子
$v->play(new class{
public $username=”我是匿名类”;
});

//核心思路 更类似于 伪协议的data伪协议

序列化于反序列化

如果属性权限为private,那么序列化后,存储的属性名字为 %00+类名+%00+属性名

如果属性权限为protected,那么序列化后,存储的属性名字为 %00+*+%00+属性名

序列化是 将 一个对象 变为一个可以传输的字符串 serialize(对象) 返回 序列化后的字符串

反序列化 就是将 一个可以传输的字符串 变为一个 可以调用的对象 unserialize(反序列化后的字符串) 返回 对象

字符串 //O:6:" ":1:{s:4:"name";N;} // O 有一个对象 // : 下一句 // 6 名字是6个字符 // : 下一句 //"Animal" 内容是Animal // : 下一句 // 1 对象有一个属性 // : 下一句 // { 对象属性描述开始 // s 属性是一个字符串 string // : 下一句 // 4 属性名字是4个字符 // : 下一句 // "name" 属性名字是name // ; 属性名字描述完了 // N 没有值,值为null // ; 属性值描述完了 // } 对象属性描述完了 class Animal{ public $name; } //结论 //反序列化和类的方法无关,不能把类方法序列化 //反序列化时,php会这么做 // 1 找到反序列化字符串规定的类名字 // 2 实例化这个类,但是不是调用构造方法 // 3 有了实例化的类对象,对它的属性进行赋值 // 4 执行魔术方法 // 5 返回构造好的对象 1 接口是否可以序列化? 2 匿名类是否可以序列化? 3 trait 是否可以序列化? 魔术方法 总结: 1 魔术方法是一类类的方法 2 会在序列化和反序列化及其他特殊情况下,自动执行 分类: 1 __construct 在实例化一个对象(new)时,会被自动调用 不允许重复声明 可以作为非public权限属性的初始化 2 __sleep 和 __wakeup方法 序列化时自动调用__sleep方法 3 __destruct方法 析构方法 类对象将要销毁,也就是脚本执行完毕后执行清理工作时自动执行 4 __call 和__callstatic 对象执行类不存在的方法时候,会自动调用__call方法 直接执行类的不存在的静态方法时,会自动调用__callstatic方法 5 __get __set 和 __isset __unset魔术方法 __get 对不可访问属性或不存在属性 进行访问引用时自动调用 __set 对不可访问属性或不存在属性 进行写入时自动调用 6 __tostring 方法 类的实例 和字符串进行拼接或者作为字符串引用时,会自动调用__tostring方法 7 __invoke方法 当类的实例被作为函数名字执行的时候,会自动调用__invoke方法 8 __set_state 方法 文档中说 执行 var_export时自动调用 9 __debugInfo 方法的属性修饰符 执行var_dump时自动调用 10 __clone方法 当使用clone关键字 ,clone一个对象时,会自动调用 php的反序列化漏洞 1 有反序列化提交的入口 2 被反序列化的类的魔术方法,有可能被利用 1 绕过__wakeup方法 条件: 1 php5至php5.6.25 之间的版本可以绕过 2 php7到php7.0.10 直接的版本可以绕过 绕过方法: 反序列化字符串中表示属性数量的值 大于 大括号内实际属性的数量时 ,wakeup方法会被绕过 2 绕过 +号正则匹配 参数有过滤,不让输入 O:数字 的形式,试图防止反序列化某个对象 O:数字 改为 O:+数字 就可以绕过上面的O:数字 过滤 3 引用绕过相等 使用&符号表示两个变量指向相同的内存引用地址 4 16进制绕过 反序列化后的字符串 不能出现某个关键单词时,可以使用大S绕过 O:8:"backdoor":1:{s:4:"name";s:10:"phpinfo();";} O:8:"backdoor":1:{S:4:"n\97me";s:10:"phpinfo();";} $data='O:8:"backdoor":1:{S:4:"n\97me";s:15:"system(\'calc\');";}'; 5 exception 绕过 不影响析构方法执行 6 php反序列化字符逃逸 1 可以控制某个类中的属性值 2 间接控制了某个类的反序列化字符串 3 由于存在无脑过滤,字符增减,造成 描述中字符串的长度 和实际的不一致 4 从而能够逃逸出若干个字符,实现字符可控,从而闭合前面的双引号 5 实现反序列化字符串的完全可控 ### Rerference [1] [https://www.jb51.net/article/266562.htm](https://www.jb51.net/article/266562.htm)

php反序列化魔术方法详解
https://xu17.top/2025/01/16/php反序列化/
作者
XU17
发布于
2025年1月16日
许可协议
XU17