概要
PHPにおけるデータ永続化の手段として、古くから親しまれている`serialize()`と`unserialize()`関数。配列やオブジェクトといった複雑なデータ構造を、そのままの状態で文字列(バイトストリーム)に変換し、データベースへの保存やセッション管理、あるいはAPI通信における簡易的なデータ交換に活用できる非常に強力なツールです。しかし、その利便性の裏側には、PHPのオブジェクト指向の仕組みを悪用した「オブジェクトインジェクション」という深刻なセキュリティリスクが潜んでいます。本稿では、PHPエンジニアが知っておくべき`serialize`の内部構造から、安全な実装方法、そしてPHP 7.0以降で推奨される`__serialize()`と`__unserialize()`を用いた最新のベストプラクティスまでを網羅的に解説します。
詳細解説:serializeの仕組みとデータ構造
`serialize()`関数は、PHPの値をバイトストリーム形式の文字列に変換します。例えば、文字列、整数、配列、オブジェクトなどが対象となり、復元時には`unserialize()`によって元の型を維持したままPHPの変数として再構築されます。
このプロセスの根幹にあるのは、クラスのメタデータを含めた情報の保持です。オブジェクトをシリアライズすると、クラス名、プロパティ名、そしてその値が保持されます。しかし、ここで注意すべきは、シリアライズされるのは「データ」のみであり、「ロジック(メソッド)」そのものはシリアライズされないという点です。復元時には、現在の実行環境(スコープ)にあるクラス定義を参照してインスタンスが再構築されます。
この仕様が問題を引き起こすのが、PHPの「マジックメソッド」です。特に`__wakeup()`や`__destruct()`は、`unserialize()`の実行時やスクリプト終了時に自動的に呼び出されます。もし、これらのメソッド内に「ユーザー入力値を引数に取る危険な処理(例:ファイル削除、データベース操作、コマンド実行)」が存在していた場合、攻撃者はシリアライズされた文字列を改ざんすることで、本来意図しない処理を強制的に実行させることが可能です。これが「PHPオブジェクトインジェクション」と呼ばれる攻撃のメカニズムです。
サンプルコード:安全な実装とバージョン移行
PHP 7.4以降では、従来の`__sleep()`や`__wakeup()`に代わり、より直感的で安全な`__serialize()`と`__unserialize()`メソッドの使用が推奨されています。以下に、現代的なPHPでの実装例を示します。
class UserSession {
public string $username;
public array $permissions;
private string $internalToken;
public function __construct(string $username, array $permissions, string $internalToken) {
$this->username = $username;
$this->permissions = $permissions;
$this->internalToken = $internalToken;
}
// PHP 7.4以降の推奨:シリアライズ時の制御
public function __serialize(): array {
// 機密情報である$internalTokenを保存対象から外すなどの制御が可能
return [
'username' => $this->username,
'permissions' => $this->permissions,
];
}
// PHP 7.4以降の推奨:復元時の制御
public function __unserialize(array $data): void {
$this->username = $data['username'];
$this->permissions = $data['permissions'];
$this->internalToken = 'default_token'; // 復元時にデフォルト値を設定
}
}
// 利用例
$user = new UserSession('admin', ['read', 'write'], 'secret_key_123');
$serialized = serialize($user);
// 復元
$unserialized = unserialize($serialized);
print_r($unserialized);
実務アドバイス:セキュリティと代替案
実務において`unserialize()`を扱う際は、以下の鉄則を遵守してください。
1. **外部からの入力には絶対に使用しない**
最も重要なルールは、「ユーザーが操作可能な文字列を`unserialize()`に渡さない」ことです。Cookieにシリアライズしたデータを保存したり、POSTパラメータで受け取った値をそのまま復元したりするのは、極めて危険な設計です。
2. **JSONへの移行を検討する**
データ永続化の目的が単なる設定値の保存や通信である場合、`json_encode()`と`json_decode()`の使用を強く推奨します。JSONは言語に依存しないフォーマットであり、オブジェクトのインジェクションといったPHP特有の脆弱性が存在しません。`json_decode()`は単なるデータの構造体しか生成せず、`__wakeup()`のようなコード実行を伴うマジックメソッドをトリガーしません。
3. **どうしてもunserializeが必要な場合**
もしレガシーなシステムで`unserialize()`が避けられない場合は、必ず`unserialize($data, [‘allowed_classes’ => [‘UserSession’]])`のように第2引数で許可するクラスを明示的に制限してください。これにより、意図しないクラスがインスタンス化されるリスクを最小化できます。
4. **型安全性を確保する**
復元されたオブジェクトに対しては、必ず`instanceof`演算子を使用して型チェックを行い、予期せぬオブジェクトが混入していないかを確認する防御的プログラミングを徹底しましょう。
まとめ
`serialize`と`unserialize`は、PHPにおける柔軟なデータ表現を支える強力な機能ですが、その裏にはオブジェクトインジェクションという強大なセキュリティリスクが存在します。現代のPHP開発においては、可能な限りJSONを活用し、どうしてもシリアライズが必要な場面では`__serialize()`を用いた精緻な制御と、許可されたクラスのみを対象とする厳格なフィルタリングを組み合わせるのが、プロフェッショナルとしての最低限の責務です。
技術の本質を理解し、そのツールが「いつ使うべきか」「いつ避けるべきか」を判断できる能力こそが、熟練のバックエンドエンジニアに求められる資質です。本稿を参考に、より堅牢で安全なコードベースを構築してください。
