【PHP8.x】OutOfRangeException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、unserialize()関数によってシリアライズされたオブジェクトが復元される際に、自動的に呼び出される処理を実行するメソッドです。これはPHPのマジックメソッドの一つとして定義されています。所属クラスであるOutOfRangeExceptionは、配列などで範囲外のインデックスへ不正にアクセスしようとした場合に発生するエラー、すなわち例外を表すためのクラスです。通常、例外オブジェクトはエラー発生時のプログラムの状態を記録するものであり、シリアライズして保存し、後から復元するような使い方は想定されていません。そのため、OutOfRangeExceptionクラスにおける__wakeupメソッドは、このクラスのオブジェクトがunserialize()されることを明確に禁止する役割を担っています。もし開発者が誤ってOutOfRangeExceptionオブジェクトを復元しようとすると、この__wakeupメソッドが例外をスローし、処理を強制的に停止させます。これにより、プログラムの意図しない状態での再開を防ぎ、システムの安定性とセキュリティを確保する仕組みとなっています。
構文(syntax)
1final public __wakeup(): void
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP __wakeup バイパスの修正確認
1<?php 2 3/** 4 * __wakeup() の挙動を検証するためのクラス 5 * 6 * __wakeup() は unserialize() 時に自動的に呼び出されるマジックメソッドです。 7 * かつて、PHPにはシリアライズされた文字列を改変することで __wakeup() の呼び出しを 8 * スキップ(バイパス)できてしまう脆弱性 (CVE-2016-7124) が存在しました。 9 * 10 * このサンプルコードは、現在のPHP(バージョン7.1.0以降)では 11 * その脆弱性が修正されており、__wakeup() が正しく呼び出されることを示します。 12 */ 13class WakeupDemo 14{ 15 private string $message; 16 17 public function __construct(string $message) 18 { 19 $this->message = $message; 20 echo 'インスタンスが生成されました: "' . $this->message . '"' . PHP_EOL; 21 } 22 23 /** 24 * unserialize() 時に呼び出されるマジックメソッド 25 * 26 * オブジェクトが復元される際に、プロパティを初期化し直します。 27 * 脆弱性があったバージョンでは、このメソッドの実行を回避できました。 28 */ 29 public function __wakeup(): void 30 { 31 echo '__wakeup() が呼び出されました。' . PHP_EOL; 32 $this->message = 'woke up!'; 33 } 34 35 /** 36 * メッセージを取得します。 37 */ 38 public function getMessage(): string 39 { 40 return $this->message; 41 } 42} 43 44// 1. 通常のオブジェクトを生成し、シリアライズする 45$obj = new WakeupDemo('initial data'); 46$serialized = serialize($obj); 47echo '元のシリアライズ文字列: ' . $serialized . PHP_EOL; 48 49// 2. 脆弱性を悪用しようと試みるペイロードを作成 50// オブジェクトのプロパティ数を実際の数(1)より大きい数(2)に書き換える 51$payload = str_replace('O:10:"WakeupDemo":1:', 'O:10:"WakeupDemo":2:', $serialized); 52echo '改変したペイロード: ' . $payload . PHP_EOL; 53echo '---' . PHP_EOL; 54 55// 3. 改変したペイロードをデシリアライズする 56echo '改変したペイロードをデシリアライズします...' . PHP_EOL; 57$unserializedObj = unserialize($payload); 58 59// 4. 結果の確認 60// PHP 8 では脆弱性が修正されているため、__wakeup() が正常に呼び出され、 61// メッセージが 'woke up!' に書き換えられていることがわかる。 62// もし脆弱性があれば、__wakeup() は実行されず、メッセージは 'initial data' のままとなる。 63echo '復元されたオブジェクトのメッセージ: "' . $unserializedObj->getMessage() . '"' . PHP_EOL; 64
このPHPコードは、unserialize()関数でオブジェクトが復元される際に自動的に呼び出されるマジックメソッド__wakeup()の挙動を説明するものです。このメソッドは引数を持たず、戻り値もありません。オブジェクトが復元された直後に行うべき初期化処理(例:データベース接続の再確立)を定義するために使用されます。
サンプルコードは、過去のPHPに存在したセキュリティ脆弱性(CVE-2016-7124)が、現在のバージョンでは修正されていることを示しています。この脆弱性は、シリアライズされた文字列内のプロパティ数を意図的に改変することで、unserialize()実行時に__wakeup()メソッドの呼び出しをスキップできてしまうというものでした。
コードでは、この攻撃を模倣するために、一度シリアライズした文字列のプロパティ数を偽装しています。しかし、PHP 8でこの改変された文字列をunserialize()で復元すると、脆弱性が修正されているため__wakeup()メソッドは正しく呼び出されます。その結果、オブジェクトのプロパティが__wakeup()内で定義された値に書き換えられ、セキュリティ対策が機能していることを確認できます。
__wakeupは、unserialize()でオブジェクトが復元される際に自動で呼び出されるマジックメソッドです。このサンプルコードは、かつてシリアライズデータを改ざんして__wakeupの実行を回避できた脆弱性が、現在のPHPでは修正されていることを示します。ただし、最も重要な注意点はunserialize()関数そのものの危険性です。信頼できない外部データをデシリアライズすると、意図しないコードが実行される深刻な脆弱性につながる可能性があります。データの保存や交換には、原則としてより安全なJSON形式とjson_decode()を使用してください。
PHP __wakeup で接続を再確立する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * __wakeup マジックメソッドの使用例を示すクラス 7 * 8 * __wakeupは、オブジェクトが unserialize() によって 9 * 文字列から復元される際に自動的に呼び出されます。 10 * データベース接続の再確立など、リソースの再初期化処理によく利用されます。 11 */ 12class Connection 13{ 14 // 接続先のホスト名 15 public string $host; 16 // 接続状態を示すプロパティ (transient: 一時的なもの) 17 private mixed $handler = null; 18 19 public function __construct(string $host) 20 { 21 $this->host = $host; 22 $this->connect(); 23 } 24 25 /** 26 * 接続を確立する(シミュレーション) 27 */ 28 private function connect(): void 29 { 30 echo "データベース ({$this->host}) に接続しました。" . PHP_EOL; 31 $this->handler = true; // 接続成功をシミュレート 32 } 33 34 /** 35 * オブジェクトが serialize() されるときに呼び出されるマジックメソッド 36 * シリアル化したいプロパティ名の配列を返す 37 */ 38 public function __sleep(): array 39 { 40 echo "__sleep() が呼び出されました。接続を一時的に閉じます。" . PHP_EOL; 41 // $handler は接続リソースでありシリアル化できないため、対象から除外する 42 return ['host']; 43 } 44 45 /** 46 * オブジェクトが unserialize() されるときに呼び出されるマジックメソッド 47 * オブジェクトの状態を復元する処理を記述する 48 */ 49 public function __wakeup(): void 50 { 51 echo "__wakeup() が呼び出されました。接続を再確立します。" . PHP_EOL; 52 // デシリアライズ時に、失われたデータベース接続を再確立する 53 $this->connect(); 54 } 55 56 /** 57 * 接続状態を確認するメソッド 58 */ 59 public function isConnected(): bool 60 { 61 return $this->handler !== null; 62 } 63} 64 65// 1. オブジェクトを生成する 66$connection = new Connection('localhost'); 67echo '接続状態: ' . ($connection->isConnected() ? 'OK' : 'NG') . PHP_EOL; 68echo PHP_EOL; 69 70// 2. オブジェクトをシリアライズ(文字列に変換)する 71// このとき __sleep() が呼び出され、接続リソースが破棄される準備がされる 72$serializedData = serialize($connection); 73echo "シリアライズ後のデータ: " . $serializedData . PHP_EOL; 74echo PHP_EOL; 75 76// $connectionオブジェクトを破棄(シミュレーション) 77unset($connection); 78 79// 3. 文字列からオブジェクトをデシリアライズ(復元)する 80// このとき __wakeup() が呼び出され、接続が再確立される 81$restoredConnection = unserialize($serializedData); 82echo '接続状態: ' . ($restoredConnection->isConnected() ? 'OK' : 'NG') . PHP_EOL; 83
PHPの__wakeup()は、「マジックメソッド」と呼ばれる特殊なメソッドの一つです。このメソッドは、unserialize()関数によってオブジェクトが文字列から復元(デシリアライズ)される際に、自動的に呼び出されます。このメソッドに引数はなく、また、何も値を返す必要はありません(戻り値なし)。
このサンプルコードでは、データベース接続を模したConnectionクラスで__wakeup()が使われています。まず、serialize()関数でオブジェクトを文字列に変換する際には、対になる__sleep()メソッドが呼び出されます。__sleep()メソッド内では、シリアル化できない接続情報($handlerプロパティ)を意図的に除外し、接続が一時的に閉じられる準備をしています。
そして、unserialize()関数を使って、シリアル化された文字列からオブジェクトを復元する際に、__wakeup()メソッドが自動的に実行されます。このメソッド内では、失われていたデータベース接続を再確立するためにconnect()メソッドが再度呼び出されます。このように__wakeup()は、オブジェクトが復元された際に、データベース接続の再確立やファイルの再オープンなど、必要なリソースを再初期化する目的で利用されます。シリアル化によって失われた状態を、デシリアライズのタイミングで安全に復元するための重要な仕組みです。
__wakeupは、unserialize関数でオブジェクトを復元する際に自動で実行される特殊なメソッドです。サンプルコードのように、データベース接続のようなシリアル化できないリソースを再初期化する目的で利用されます。serialize時に呼ばれる__sleepメソッドと対になっており、__sleepで接続などの一時的な状態を整理し、__wakeupでそれを再構築するのが一般的な使い方です。最も重要な注意点はセキュリティです。信頼できない外部データをunserializeすると、__wakeup内の処理が意図せず実行され、脆弱性につながる可能性があります。unserializeに渡すデータは、必ず信頼できるものに限定してください。