php反序列化基础
php反序列化基础
seoraphp反序列化基础
什么是序列化,反序列化
反序列化(Deserialization) 是指将序列化后的数据(通常是字符串或二进制格式)恢复为原始数据结构(如数组、对象)。在 PHP 中,unserialize() 就是用来执行反序列化的,它可以把 serialize() 处理过的字符串还原成原始的 PHP 变量或对象。
示例:
1 | <?php |
输出:
1 | 序列化后的数据: a:2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;} |
这个过程就是:
- 序列化:把数组转换成字符串(便于存储、传输)。
- 反序列化:把字符串恢复成数组(便于程序使用)。
程序语言之所以需要序列化(Serialization)和反序列化(Deserialization),主要是为了数据的存储、传输和共享。具体来说:
在应用开发中,数据往往需要持久化(如存入数据库、缓存或文件)。但数据库和文件通常存储的是字符串或二进制格式,序列化可以将复杂的数据结构(如对象、数组)转换为可存储的格式,之后用反序列化恢复出来。
序列化 = 把数据转换成字符串/二进制(方便存储和传输)
反序列化 = 把字符串/二进制恢复成数据(方便程序使用)
PHP 序列化的代号与释义
| 代号 | 数据类型 | 释义 | 示例 |
|---|---|---|---|
N |
NULL |
表示 NULL 值 |
N; |
b |
boolean |
布尔值 true 或 false |
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 | <?php |
输出:
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 $name和private $password)。s:4:"name";s:5:"Alice";→public $name的值是"Alice"。s:12:"Userpassword";s:9:"secret123";→private $password的值是"secret123"。
🔹 但 sayHello() 方法并没有出现在序列化结果中,说明方法不会被序列化。
2.
在反序列化的时候要保证有该类存在,因为没有序列化方法,所以我们反序列化回来还要依靠该类的方法进行。
为什么要序列化对象成字符串呢?在开发中又起到什么作用?
因为PHP文件执行后会把内存的数据进行销毁,如果下一个文件想用到刚刚销毁对象的属性和值就还要重新实例化new一次对象,所以才会将对象进行序列化然后存储,也避免重新实例化带来的耗费。
3.
什么是魔术方法
魔术方法是一种特殊的方法,会在对象执行某些操作时覆盖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 | (1)echo ($obj) / print($obj) 打印时会触发 |
魔术方法总结
反序列化漏洞的成因:
在 PHP 反序列化的过程中,如果 unserialize() 处理的是用户可控的输入,那么攻击者就可以伪造对象的序列化字符串,修改原有类的属性值,甚至触发类中的魔术方法,从而执行任意代码。
反序列化漏洞是由于unserialize函数接收到了恶意的序列化数据篡改成员属性后导致的。
[NewStarCTF 公开赛赛道]UnserializeOne
1 | class Start{ |
Start- 析构方法
__destruct():输出Welcome to NewStarCTF, $this->name; __isset()方法:会调用$this->func这个属性
- 析构方法
Sec__toString()方法:调用$this->obj->check($this->var);__invoke()方法:读取/flag并输出。
Easy__call()魔术方法:$this->cla = clone $var[0];(调用时克隆$var[0])
eeee__clone()方法:检查$this->obj->cmd是否存在。
目标是 触发 Sec 类的 __invoke() 方法,这样可以 file_get_contents('/flag') 读取 flag。
1 | 要 触发 Sec 类的 `__invoke()` 方法->把对象当作函数调用:->Start类的_isset方法:把Sec类当作func属性 |
后续就是编写exp
先将代码复制进vscode,然后把所有和属性无关的删除。
private的属性改成public 缺的属性补全
1 | <?php |
[NewStarCTF 2023 公开赛道]POP Gadget
1 | <?php |
入手点: ($this->func)($this->var); 可以执行任意命令
1 | WhiteGod::_unset->CTF::end->Handle::_call->Super::_invoke->Then::toString->Begin::__destruct |









