PHP反序列化原理详解
引言
PHP反序列化是PHP中一个重要的概念,它允许将序列化后的数据重新转换为原始的数据结构。在PHP中,可以使用serialize()函数将数据序列化为字符串,然后使用unserialize()函数将序列化后的字符串反序列化为原来的数据结构。这个过程在数据存储、传输和对象持久化等方面起着关键作用。然而,不当使用反序列化可能导致安全漏洞,因此理解其原理和正确使用方法至关重要。
原理
- 序列化: PHP中的serialize()函数用于将数据(如数组、对象)转换为字符串格式,以便存储或传输。序列化后的字符串包含数据类型和值的信息。
- 反序列化: unserialize()函数用于将序列化后的字符串转换回PHP值,如数组或对象。在反序列化过程中,PHP根据序列化字符串中的信息重新构建原始数据结构。
PHP序列化原理演示
序列化
<?php
class Test {
// 声明属性
public $publicVar = "public value";
private $privateVar = "private value";
protected $protected = "protected value";
// 声明方法
public function printVar() {
echo $this->publicVar . "\n";
echo $this->privateVar . "\n";
echo $this->protected . "\n";
}
}
$test = new Test();
$test->printVar();
$testSerial = serialize($test);
echo $testSerial;
当一个方法在类定义内部被调用时,有一个可用的伪变量 t h i s 。 this。 this。this 是一个到当前对象的引用。
运行结果
public value
private value
protected value
O:4:"Test":3:{s:9:"publicVar";s:12:"public value";s:16:"TestprivateVar";s:13:"private value";s:12:"*protected";s:15:"protected value";}
将序列化之后的值格式化来分析
O:4:"Test":3:{
s:9:"publicVar";
s:12:"public value";
s:16:"TestprivateVar";
s:13:"private value";
s:12:"*protected";
s:15:"protected value";
}
O:4:"Test":3:{
:这表示一个对象(O
),其类名长度为4("Test"
),并且有3个属性。- 第一个属性:
s:9:"publicVar";
:这是一个字符串(s
),长度为9,内容为"publicVar"
,表示属性名。s:12:"public value";
:这是一个字符串,长度为12,内容为"public value"
,表示公共属性的值。
- 第二个属性:
s:16:"TestprivateVar";
:这是一个字符串,长度为16,内容为"TestprivateVar"
,表示私有属性的名称。在PHP中,私有属性在序列化时会以类名作为前缀。s:13:"private value";
:这是一个字符串,长度为13,内容为"private value"
,表示私有属性的值。
- 第三个属性:
s:12:"*protected";
:这是一个字符串,长度为12,内容为"*protected"
,表示受保护属性的名称。在PHP中,受保护属性在序列化时会以星号(*
)作为前缀。s:15:"protected value";
:这是一个字符串,长度为15,内容为"protected value"
,表示受保护属性的值。
- 第一个属性:
公共属性很好理解,但私有属性和保护属性就不一样了
protected:受保护成员只能在其定义的类及其子类中访问。
序列化后属性名 \x00\*\x00属性名
**private:**私有成员只能在其定义的类内部访问,不能在子类或类外部访问。
序列化后属性名 \x00类名\x00属性名
注意:浏览器输出默认不显示\x00
PHP序列化字符串中使用的类型标识符及其对应的数据类型
类型标识符 | 数据类型 | 描述 |
---|---|---|
s | 字符串(String) | 表示一个字符串,例如 s:5:"hello" 。 |
i | 整数(Integer) | 表示一个整数,例如 i:123 。 |
d | 浮点数(Double) | 表示一个浮点数,例如 d:3.14 。 |
b | 布尔值(Boolean) | 表示一个布尔值,b:1 表示 true,b:0 表示 false。 |
a | 数组(Array) | 表示一个数组,例如 a:2:{i:0;s:5:"apple";i:1;s:6:"banana"} 。 |
O | 对象(Object) | 表示一个对象,例如 O:6:"MyClass":1:{s:3:"foo";s:5:"bar"} 。 |
N | NULL值 | 表示 NULL 值,例如 N; 。 |
r | 引用(Reference) | 表示指向当前对象中的引用,例如 r:2; 。 |
R | 引用(Reference) | 表示指向序列化数据中另一个对象的引用,例如 R:3; 。 |
C | 自定义序列化(Custom serialization) | 表示自定义序列化的对象,例如 C:6:"MyClass":0:{} 。 |
魔术方法
魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。
魔术方法 | 功能描述 |
---|---|
__construct() |
构造方法,在对象创建时自动调用。 |
__destruct() |
析构方法,在对象被销毁前自动调用。 |
__call() |
在调用不可访问的方法时自动调用。 |
__callStatic() |
在静态上下文中调用不可访问的方法时自动调用。 |
__get() |
在读取不可访问的属性时自动调用。 |
__set() |
在设置不可访问的属性时自动调用。 |
__isset() |
在使用isset() 或empty() 检测不可访问的属性时自动调用。 |
__unset() |
在使用unset() 销毁不可访问的属性时自动调用。 |
__sleep() |
在序列化对象之前自动调用,用于清理对象或返回一个包含要序列化的属性名的数组。 |
__wakeup() |
在反序列化对象之后自动调用,用于重建对象。 |
__serialize() |
自定义序列化逻辑,返回一个数组,包含要序列化的属性和值。PHP 7.4+ |
__unserialize() |
自定义反序列化逻辑,接收一个数组,包含序列化的属性和值。PHP 7.4+ |
__toString() |
在尝试将对象转换为字符串时自动调用。返回对象的字符串表示。 |
__invoke() |
当尝试将对象作为函数调用时自动调用。 |
__set_state() |
在使用var_export() 导出类时,此静态方法会被调用,用于恢复类的属性。 |
__clone() |
在对象被复制时自动调用,用于自定义克隆逻辑。 |
__debugInfo() |
在使用var_dump() 打印对象时自动调用,用于提供调试信息。PHP 5.6+ |
<?php
class Test {
// 声明属性
public $publicVar = "public value";
private $privateVar = "private value";
protected $protected = "protected value";
// 声明方法
public function printVar() {
echo $this->publicVar . "\n";
echo $this->privateVar . "\n";
echo $this->protected . "\n";
}
public function __wakeup() {
echo '__wakeup';
}
}
$test = new Test();
$test->printVar();
$testSerial = serialize($test);
echo $testSerial;
echo "\n";
unserialize($testSerial);
可以看到运行最后调用了 __wakeup
魔术方法
public value
private value
protected value
O:4:"Test":3:{s:9:"publicVar";s:12:"public value";s:16:" Test privateVar";s:13:"private value";s:12:" * protected";s:15:"protected value";}
__wakeup
如果__wakeup方法中有高敏感度代码,例如命令执行等,可能会导致意外的风险
例如:
<?php
class Test {
public $cmd = '';
public function __wakeup() {
system($this->cmd);
}
}
unserialize($_GET['data']);
传入?data=O:4:"Test":1:{s:3:"cmd";s:2:"id";}
即可成功返回命令执行结果