序列化 是將物件/陣列轉為可儲存/傳輸的字串;反序列化 是還原過程。PHP 提供 serialize()/unserialize() 原生格式,但現代架構常選 JSON、MessagePack、igbinary。本文比較各方案特性,並重點剖析 反序列化漏洞 (Object Injection) 防範。
<?php
$data = ['user' => 'Alice', 'roles' => ['admin', 'editor'], 'meta' => (object)['id' => 123]];
$serialized = serialize($data);
// 輸出: a:3:{s:4:"user";s:5:"Alice";s:5:"roles";a:2:{i:0;s:5:"admin";i:1;s:6:"editor";}s:4:"meta";O:8:"stdClass":1:{s:2:"id";i:123;}}
$restored = unserialize($serialized);
var_dump($restored === $data); // true (值相等),但物件身份不同
// 物件序列化需類別定義存在
class User {
public string $name;
protected string $email;
private int $id;
public function __construct(string $name, string $email, int $id) {
$this->name = $name; $this->email = $email; $this->id = $id;
}
public function __sleep() { return ['name', 'email']; } // 排除私有 $id
public function __wakeup() { $this->id = random_int(1, 1000); } // 還原時重新生成
}
$user = new User('Bob', 'bob@example.com', 42);
$ser = serialize($user); // O:4:"User":2:{s:4:"name";s:3:"Bob";s:5:"email";s:16:"bob@example.com";}
$obj = unserialize($ser); // 呼叫 __wakeup()
?>
| 方法 | 觸發時機 | 回傳值/用途 |
|---|---|---|
__sleep() | serialize() 前 | 陣列:指定要序列化的屬性名 (排除敏感/資源屬性) |
__wakeup() | unserialize() 後 | void:重新建立資源連線、初始化狀態 |
__serialize() | PHP 7.4+,serialize() 時 | 陣列:完全自定義序列化資料結構 (取代 __sleep) |
__unserialize() | PHP 7.4+,unserialize() 時 | void:從陣列還原狀態 (取代 __wakeup) |
序列化字串含特殊字符 (:",;{}) 若直接存入 DB/Redis/URL 易導致截斷或注入。常見方案:
<?php
// 安全儲存/傳輸封裝
function safeSerialize(mixed $data): string {
return base64_encode(serialize($data));
}
function safeUnserialize(string $str): mixed {
$data = @base64_decode($str, true); // strict 模式
if ($data === false) throw new InvalidArgumentException('Invalid base64');
return @unserialize($data);
}
// 進階:壓縮 + Base64 (大物件)
function compactSerialize(mixed $data): string {
return base64_encode(gzcompress(serialize($data), 6)); // 壓縮等級 6
}
function compactUnserialize(string $str): mixed {
$decoded = base64_decode($str, true);
if ($decoded === false) throw new InvalidArgumentException('Invalid base64');
$decompressed = @gzuncompress($decoded);
if ($decompressed === false) throw new RuntimeException('Decompress failed');
return @unserialize($decompressed);
}
?>
| 維度 | PHP serialize | JSON | igbinary | MessagePack |
|---|---|---|---|---|
| PHP 專屬 | 是 | 否 (通用) | 是 | 否 (通用) |
| 支援物件/類別 | 完整 (含私有/保護屬性) | 否 (僅 public 屬性,stdClass) | 完整 | 否 (需映射) |
| 體積 | 中等 | 較大 (文字) | 小 (二進位、無標籤) | 極小 (二進位、Schema-less) |
| 速度 (序列化/反序列化) | 中 | 快 (C 實現) | 極快 (C 擴充,~2-3x serialize) | 極快 |
| 人類可讀 | 半可讀 | 是 | 否 | 否 |
| 跨語言 | 否 | 是 (標準) | 否 | 是 (多語言庫) |
| 安裝 | 內建 | 內建 | 需擴充 pecl install igbinary | 需擴充 pecl install msgpack |
| 適用場景 | Session、緩存物件、舊專案相容 | API、配置、跨語言、日誌 | 高效能緩存 (Redis/Memcached)、Session | 微服務通訊、高吞吐佇列、嵌入式 |
當不可信資料傳入 unserialize(),攻擊者可構造惡意物件鏈,觸發 __wakeup()/__destruct()/__toString() 等魔術方法,導致 RCE、檔案讀寫、SQL 注入等。
// 攻擊者構造 payload (簡化版)
class Evil {
public $cmd = 'rm -rf /';
public function __destruct() { system($this->cmd); }
}
// 序列化後: O:4:"Evil":1:{s:3:"cmd";s:8:"rm -rf /";}
// 若應用: unserialize($_COOKIE['data']) -> 災難
unserialize():Cookie、Request 參數、HTTP Header、資料庫使用者可控欄位。json_decode($input, true) 只產生陣列/標量,無物件實例化風險。unserialize($data, ['allowed_classes' => false]) 禁止所有類別實例化 (只還原陣列/標量)。['allowed_classes' => ['MySafeClass', 'AnotherClass']] 白名單模式。hash_hmac('sha256', serialize($data), $secret),讀取時先驗簽名再反序列化。session.serialize_handler = php_serialize (預設),搭配 session.use_strict_mode = 1、HTTPS Only Cookie、定期輪換 Session ID。php.ini 設定 unserialize_allowed_classes (PHP 8.4+ 提議) 或自行封裝安全解析器。session.serialize_handler=igbinary)。serialize() + Base64 + HMAC 簽名 + allowed_classes 白名單。unserialize($userInput) = 遠端代碼執行風險。