serialize() 將 PHP 變量 (標量、陣列、對象) 轉為可存儲/傳輸的字串;unserialize() 還原。本文詳解機制、魔術方法、對象序列化細節、安全漏洞與現代替代方案。
<?php
// 支援類型:所有標量、陣列、對象
// 不支援:resource (資源句柄)
$arr = ['key' => 'website', 'value' => 'www.example.com'];
$str = serialize($arr);
// 輸出: a:2:{s:3:"key";s:7:"website";s:5:"value";s:17:"www.example.com";}
$restored = unserialize($str);
// $restored === $arr (值相等,但非同一引用)
?>
<?php
class Dog {
public $name;
public $age;
public $owner;
public function __construct($name = 'unnamed', $age = 0, $owner = 'unknown') {
$this->name = $name;
$this->age = $age;
$this->owner = $owner;
}
public function getAgeInDays() {
return $this->age * 365;
}
// 序列化前調用:可清理資源、關閉連接、決定保存哪些屬性
public function __sleep() {
// 返回需序列化的屬性名陣列
return ['name', 'age', 'owner']; // 排除敏感/不可序列化屬性
}
// 反序列化後調用:重建連接、初始化狀態
public function __wakeup() {
// $this->db = new PDO(...);
}
}
// 使用
$dog = new Dog('Rover', 12, 'Lisa and Graham');
$serialized = serialize($dog);
// O:3:"Dog":3:{s:4:"name";s:5:"Rover";s:3:"age";i:12;s:5:"owner";s:15:"Lisa and Graham";}
unset($dog); // 銷毀原對象
$pet = unserialize($serialized); // 還原,自動調用 __wakeup()
echo $pet->getAgeInDays(); // 4380
?>
| 版本 | 對象序列化行為 |
|---|---|
| PHP 3 | 僅序列化屬性,方法丟失 |
| PHP 4+ | 屬性+方法均保留 (類定義必須存在) |
| PHP 7+ | 增加 __serialize()/__unserialize() (PHP 7.4) 更精細控制 |
<?php
// 1. Session 存儲用戶對象
$_SESSION['user'] = serialize($userObj);
// 取出
$userObj = unserialize($_SESSION['user']);
// 2. Redis 緩存複雜數據
$redis->setex('user:1001', 3600, serialize($userProfile));
// 3. 隊列任務傳遞對象
$payload = serialize(new SendEmailJob($to, $subject, $body));
$queue->push($payload);
// Worker 端
$job = unserialize($payload);
$job->handle();
?>
核心危害:攻擊者控制輸入字串,構造惡意對象,利用 `__wakeup()`、`__destruct()`、魔術方法鏈 (POP Chain) 觸發 RCE、文件讀寫、SQL注入等。
<?php
// 易受攻擊代碼
$data = $_COOKIE['user_data'];
$user = unserialize($data); // 直接反序列化用戶輸入!
// 攻擊者構造:
// O:8:"SplFileObject":2:{s:4:"file";s:11:"/etc/passwd";s:8:"flags";i:0;}
// 利用 SplFileObject __destruct() 讀取文件
?>
json_encode()/json_decode() 無代碼執行風險unserialize($data, ['allowed_classes' => ['User', 'Order']])| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| JSON | 安全、跨語言、可讀、標準 | 不支援對象方法、資源、循環引用 | API、配置、緩存、隊列 (首選) |
| igbinary | 二進制、體積小 50%、速度快、支援對象 | 需安裝擴展、仍有反序列化風險 | 高性能緩存、Session (配合簽名) |
| MessagePack | 跨語言、二進制、Schema-less | 需擴展、PHP 對象還原需映射 | 異構微服務通訊 |
| Protocol Buffers | 強 Schema、極快、向後兼容 | 需定義 .proto、學習曲線 | 高性能內部 RPC、存儲 |
| serialize() | 原生、支援完整對象圖 | 安全風險、體積大、PHP 專用 | 遺遺系統維護、極簡場景 (配合簽名+白名單) |
<?php
class User {
public $id;
public $name;
private $passwordHash; // 敏感字段
public function __serialize(): array {
return [
'id' => $this->id,
'name' => $this->name,
// 故意排除 passwordHash
];
}
public function __unserialize(array $data): void {
$this->id = $data['id'];
$this->name = $data['name'];
$this->passwordHash = ''; // 重置
}
}
?>
優於 `__sleep`/`__wakeup`:類型安全、不可繼承衝突、配合 `allowed_classes` 更精準。
serialize()/unserialize() 功能強大但**安全風險極高**__serialize/__unserialize 獲得更好控制