PHP 的 serialize() 將變數(陣列、物件、純量)轉為可儲存的字串格式,unserialize() 則還原。常用於 Session 儲存、快取、API 傳遞、資料庫欄位存陣列等場景。但序列化也隱藏著嚴重安全風險,本文深度解析原理、用法與防範之道。
<?php
// 陣列序列化
$array = ['name' => 'John', 'age' => 30, 'tags' => ['php', 'dev']];
$serialized = serialize($array);
// 輸出:a:3:{s:4:"name";s:4:"John";s:3:"age";i:30;s:4:"tags";a:2:{i:0;s:3:"php";i:1;s:3:"dev";}}
// 還原
$restored = unserialize($serialized);
var_dump($restored); // 結構完全一致
// 物件序列化(會呼叫 __sleep / __wakeup)
class User {
public $name;
private $token;
public function __construct($name, $token) {
$this->name = $name; $this->token = $token;
}
public function __sleep() { return ['name']; } // 不序列化敏感 token
public function __wakeup() { $this->token = 're-generated'; }
}
$user = new User('Alice', 'secret123');
$ser = serialize($user);
// O:4:"User":1:{s:4:"name";s:5:"Alice";} // token 未被序列化
$newUser = unserialize($ser); // 觸發 __wakeup
| 類型 | 標記 | 範例 |
|---|---|---|
| Null | N | N; |
| 布林 | b | b:1; |
| 整數 | i | i:42; |
| 浮點數 | d | d:3.14; |
| 字串 | s | s:5:"hello"; |
| 陣列 | a | a:2:{i:0;s:3:"foo";i:1;s:3:"bar";} |
| 物件 | O | O:8:"stdClass":1:{s:3:"foo";s:3:"bar";} |
| 參考 | R/r | r:1; 參考第 1 個元素 |
$a = ['foo' => 'bar'];
$b = &$a;
$ser = serialize([$a, $b]);
// 還原後 $restored[0] 和 $restored[1] 仍為同一參考
class Data {
private $pdo; // 資源無法序列化
private $config;
public function __sleep() {
return ['config']; // 僅儲存 config
}
public function __wakeup() {
$this->pdo = new PDO(...); // 重新建立連線
}
}
class CustomSerialize implements Serializable {
private $data;
public function serialize() {
return json_encode($data); // 自訂格式
}
public function unserialize($serialized) {
$this->data = json_decode($serialized, true);
}
}
// PHP 內建 Session handler 自動 serialize/unserialize
$_SESSION['cart'] = ['items' => [...], 'coupon' => 'SAVE10'];
// Redis/Memcached 值只能是字串
$cache->set('user:100', serialize($userObject));
$user = unserialize($cache->get('user:100'));
// settings 欄位存序列化陣列
$settings = ['theme' => 'dark', 'lang' => 'zh-TW'];
$db->update('users', ['settings' => serialize($settings)], ['id' => 1]);
絕對不要對不可信輸入呼叫 unserialize()!
惡意使用者構造序列化字串,指定任意類別並設定屬性,觸發 __wakeup()、__destruct()、__toString() 等魔術方法,導致 RCE、檔案刪除、SQL 注入等。
// 假設系統有類別 VulnerableClass 有 __destruct() 執行 system()
O:17:"VulnerableClass":1:{s:4:"cmd";s:10:"rm -rf /";}
// ❌ 危險
$userData = unserialize($_COOKIE['profile']);
// ✅ 安全:使用 JSON
$userData = json_decode($_COOKIE['profile'], true);
// 僅允許特定類別還原(仍有風險,僅作為防禦纵深)
$data = unserialize($input, ['allowed_classes' => ['MyApp\\DTO\\UserProfile']]);
function safeSerialize($data, $secret) {
$serialized = serialize($data);
$hmac = hash_hmac('sha256', $serialized, $secret);
return $hmac . ':' . $serialized;
}
function safeUnserialize($signed, $secret) {
list($hmac, $serialized) = explode(':', $signed, 2);
if (!hash_equals($hmac, hash_hmac('sha256', $serialized, $secret))) {
throw new Exception('簽名驗證失敗');
}
return unserialize($serialized, ['allowed_classes' => false]);
}
| 格式 | 優點 | 缺點 |
|---|---|---|
| JSON | 通用、安全、人類可讀 | 無型別資訊、體積較大 |
| MessagePack | 二進制、快速、支援型別 | 需擴展、不可直接閱讀 |
| Protocol Buffers | 強型別、跨語言、極小體積 | 需定義 .proto、學習曲線 |
| igbinary | PHP 專用、比 serialize 快/小 | 仍有物件注入風險 |
// 100萬次迴圈測試
serialize/unserialize: ~1.2s / 1.5s
json_encode/decode: ~0.8s / 1.0s
igbinary: ~0.6s / 0.7s
msgpack: ~0.5s / 0.6s
serialize() 方便但極度危險,僅用於完全可信資料(如內部快取、Session)unserialize( 呼叫,確認來源可信