【PHP8.x】RangeException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、シリアライズされたRangeExceptionオブジェクトのデシリアライズ(復元)を防止するメソッドです。PHPには、serialize関数を使ってオブジェクトの状態を文字列として保存し、unserialize関数でその文字列からオブジェクトを復元する仕組みがあります。__wakeupはマジックメソッドの一つで、unserialize関数によってオブジェクトが復元された直後に自動的に呼び出されます。通常、このメソッドはデータベース接続の再確立など、復元後の初期化処理を記述するために使用されます。しかし、RangeExceptionを含むPHPの組み込み例外クラスでは、セキュリティ上の理由からデシリアライズが意図的に禁止されています。もしシリアライズされたRangeExceptionオブジェクトをunserializeしようとすると、この__wakeupメソッドが呼び出され、処理を中断して新たにExceptionがスローされます。これは、悪意を持って改ざんされたシリアライズデータからオブジェクトが復元されることで、予期せぬ脆弱性が生まれることを防ぐための安全対策です。したがって、このメソッドの存在により、RangeExceptionオブジェクトはデシリアライズできないことが保証されています。
構文(syntax)
1<?php 2 3// RangeExceptionは、内部的にfinalな__wakeup()を持つためアンシリアライズできない 4try { 5 $serialized = serialize(new RangeException('範囲外です')); 6 unserialize($serialized); 7} catch (Exception $e) { 8 // "Exception: Unserialization of 'RangeException' is not allowed" 9 echo $e->getMessage(); 10}
引数(parameters)
引数なし
引数はありません
戻り値(return)
void
__wakeupメソッドは、オブジェクトがunserialize()された後に呼び出されます。このメソッドは、オブジェクトを再初期化する目的で使用され、戻り値はありません。
サンプルコード
PHP __wakeup バイパス脆弱性の挙動
1<?php 2 3/** 4 * __wakeup()マジックメソッドの挙動と、過去に存在した 5 * バイパス脆弱性(CVE-2016-7124)を説明するためのクラス。 6 * 7 * この脆弱性はPHP 7.1で修正済みのため、現在のPHPバージョンでは 8 * __wakeup()のバイパスは発生せず、デシリアライズが失敗します。 9 */ 10class WakeupDemo 11{ 12 public string $message; 13 14 public function __construct(string $message = 'Initialized') 15 { 16 $this->message = $message; 17 echo "コンストラクタが呼ばれました。 message: {$this->message}\n"; 18 } 19 20 /** 21 * unserialize()が成功した直後に自動的に呼び出されるマジックメソッド。 22 * データベース接続の再確立など、リソースの再初期化処理を記述します。 23 */ 24 public function __wakeup(): void 25 { 26 echo "__wakeup() が呼ばれました。オブジェクトを再初期化します。\n"; 27 $this->message = 'Woke up!'; 28 } 29 30 /** 31 * オブジェクトが破棄される際に自動的に呼び出されるマジックメソッド。 32 */ 33 public function __destruct() 34 { 35 echo "デストラクタが呼ばれました。 final message: {$this->message}\n"; 36 } 37} 38 39// 1. 正常なシリアライズとデシリアライズ 40echo "--- 1. 正常なケース ---\n"; 41$normal_object = new WakeupDemo('Normal'); 42$serialized_string = serialize($normal_object); 43echo "シリアライズ文字列: {$serialized_string}\n"; 44// デシリアライズ時に __wakeup() が呼び出される 45$unserialized_object = unserialize($serialized_string); 46unset($normal_object, $unserialized_object); // デストラクタを呼び出す 47echo "\n"; 48 49 50// 2. __wakeup() バイパス脆弱性の試行 51echo "--- 2. __wakeup() バイパスの試行 (現在は機能しません) ---\n"; 52// 脆弱性を利用したペイロードを作成。 53// プロパティ数(1)を実際の数より大きい値(2)に書き換える。 54// O:10:"WakeupDemo":1:{s:7:"message";s:6:"Normal";} -> O:10:"WakeupDemo":2:{...} 55$bypass_payload = 'O:10:"WakeupDemo":2:{s:7:"message";s:15:"Bypass Attemped";}'; 56echo "不正なペイロード: {$bypass_payload}\n"; 57 58// PHP 7.1以降では、この脆弱性は修正されています。 59// そのため、unserialize()は false を返し、E_NOTICE レベルの警告が発生します。 60// 結果として __wakeup() は呼び出されず、オブジェクトも生成されません。 61$result = unserialize($bypass_payload); 62 63if ($result === false) { 64 echo "デシリアライズに失敗しました。__wakeup() のバイパスはブロックされました。\n"; 65} 66// 古いPHPでは、__wakeup()がスキップされ、デストラクタが 67// "Bypass Attemped" というメッセージで実行されてしまう可能性がありました。 68
__wakeup()は、unserialize()関数によってオブジェクトが復元された直後に自動的に呼び出される、PHPの特殊なメソッド(マジックメソッド)です。このメソッドは引数を取らず、戻り値もありません(void)。オブジェクトが文字列から復元される際に、データベース接続の再確立といったリソースの再初期化処理を行う目的で使用されます。
サンプルコードの前半では、オブジェクトを一度文字列に変換(シリアライズ)し、それを元に戻す(デシリアライズ)正常な処理を示しています。unserialize()が実行されると__wakeup()が自動的に呼び出され、オブジェクトのプロパティが初期化されることが確認できます。
後半では、過去に存在した脆弱性を利用して__wakeup()の実行を意図的にスキップ(バイパス)する試みを行っています。これは、シリアライズされた文字列内のプロパティ数を不正に書き換えることで、初期化処理を回避する攻撃手法でした。しかし、この脆弱性はPHP 7.1以降で修正済みです。そのため、現在の環境でこのコードを実行すると、unserialize()は失敗してfalseを返し、__wakeup()が呼び出されることはなく、攻撃はブロックされます。
__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動で実行される特殊なメソッドで、主にリソースの再初期化に使われます。最も重要な注意点は、unserialize()関数にユーザー入力など信頼できない文字列を渡してはいけないことです。サンプルコードで示されている__wakeupの呼び出しを回避する脆弱性は、古いPHPに存在したものであり、現在のバージョンでは修正済みでデシリアライズは失敗します。しかし、unserialize()には他にも深刻な脆弱性が存在する可能性があるため、外部データを扱う際はjson_decode()のような、より安全なデータ交換フォーマットを利用することを強く推奨します。
PHP __wakeupでオブジェクトを再初期化する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * __wakeupマジックメソッドの使用例を示すクラス 7 * 8 * このクラスは、シリアライズできないリソース(例: データベース接続やファイルハンドル)を 9 * 扱う状況を模倣しています。 10 */ 11class ResourceConnector 12{ 13 /** 14 * 接続情報(シリアライズ対象) 15 */ 16 private string $resourceId; 17 18 /** 19 * 接続ハンドル(シリアライズできないリソース) 20 * unserialize後に再生成する必要がある 21 * @var mixed 22 */ 23 private $connection; 24 25 public function __construct(string $resourceId) 26 { 27 $this->resourceId = $resourceId; 28 $this->connect(); 29 } 30 31 /** 32 * オブジェクトが破棄されるときにリソースを解放する 33 */ 34 public function __destruct() 35 { 36 if ($this->connection !== null) { 37 echo "リソース '{$this->resourceId}' の接続を閉じます。" . PHP_EOL; 38 $this->connection = null; 39 } 40 } 41 42 /** 43 * serialize() が呼び出される直前に実行されるマジックメソッド 44 * 45 * シリアライズ(永続化)したいプロパティ名の配列を返す必要があります。 46 * ここでは、接続ハンドル($connection)を除外します。 47 * 48 * @return string[] 49 */ 50 public function __sleep(): array 51 { 52 echo "__sleep(): オブジェクトをシリアライズする準備をします。" . PHP_EOL; 53 return ['resourceId']; 54 } 55 56 /** 57 * unserialize() が呼び出された直後に実行されるマジックメソッド 58 * 59 * オブジェクトが復元された後に、必要なリソース(DB接続など)を 60 * 再確立するための初期化処理をここで行います。 61 */ 62 public function __wakeup(): void 63 { 64 echo "__wakeup(): オブジェクトの復元後にリソースを再初期化します。" . PHP_EOL; 65 $this->connect(); 66 } 67 68 /** 69 * リソースへの接続を模倣する 70 */ 71 private function connect(): void 72 { 73 echo "リソース '{$this->resourceId}' に接続します..." . PHP_EOL; 74 // 実際の接続リソースの代わりに、接続状態を示すオブジェクトを代入 75 $this->connection = new stdClass(); 76 } 77 78 /** 79 * 接続状態を確認する 80 */ 81 public function isConnected(): bool 82 { 83 return $this->connection !== null; 84 } 85} 86 87// 1. オブジェクトのインスタンスを生成 88$connector = new ResourceConnector('database-main'); 89echo '接続状態: ' . ($connector->isConnected() ? 'OK' : 'NG') . PHP_EOL; 90echo PHP_EOL; 91 92// 2. オブジェクトをシリアライズ(文字列に変換) 93// この処理の前に __sleep() が呼び出される 94$serializedObject = serialize($connector); 95echo "シリアライズ後の文字列: " . $serializedObject . PHP_EOL; 96echo PHP_EOL; 97 98// 元のオブジェクトはここで破棄されたと仮定する 99unset($connector); 100echo PHP_EOL; 101 102// 3. 文字列からオブジェクトをデシリアライズ(復元) 103// この処理の後に __wakeup() が呼び出され、接続が再確立される 104$restoredConnector = unserialize($serializedObject); 105echo '復元後の接続状態: ' . ($restoredConnector->isConnected() ? 'OK' : 'NG') . PHP_EOL; 106echo PHP_EOL; 107 108// スクリプト終了時に、復元されたオブジェクトのデストラクタが呼ばれる 109
PHPの__wakeupは、マジックメソッドと呼ばれる特別なメソッドの一つです。このメソッドは、unserialize()関数によって、文字列からオブジェクトが復元(デシリアライズ)された直後に自動的に呼び出されます。主な目的は、オブジェクトが復元された後の初期化処理を行うことです。
オブジェクトをserialize()関数で文字列に変換(シリアライズ)する際、データベース接続やファイルハンドルといった外部への接続情報は一緒に保存できません。そのため、オブジェクトを復元しただけでは、これらの接続は失われたままです。
サンプルコードでは、オブジェクトのシリアライズ時に__sleep()メソッドで接続情報($connectionプロパティ)を意図的に除外しています。その後、unserialize()でオブジェクトを復元すると、自動的に__wakeup()メソッドが実行されます。このメソッド内で再度connect()を呼び出すことにより、失われていたリソースへの接続を再確立し、オブジェクトをシリアライズ前と同じように利用できる状態に戻しています。
このメソッドに引数はなく、オブジェクトの内部状態を整えることが役割のため、戻り値もありません(void)。このように__wakeup()は、シリアライズによって失われた状態を、オブジェクト復元時に再構築するための重要な仕組みです。
__wakeupメソッドは、unserialize()関数によってオブジェクトが復元された直後に自動的に呼び出されるマジックメソッドです。サンプルコードのように、データベース接続やファイルハンドルなど、そのままでは保存できないリソースを復元後に再接続・再初期化する目的で使われます。シリアライズ時に不要なプロパティを除外する__sleepメソッドと対で実装するのが一般的です。重要な注意点として、unserialize関数に信頼できない文字列を渡すと、意図しないコードが実行される脆弱性につながる危険があるため、データの出所を常に検証してください。また、オブジェクトの復元時にはコンストラクタは実行されず、代わりに__wakeupが初期化の役割を担うことを覚えておきましょう。