【PHP8.x】RuntimeException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、RuntimeExceptionオブジェクトのデシリアライズを意図的に失敗させる処理を実行するメソッドです。このメソッドはPHPのマジックメソッドの一つで、unserialize()関数によってシリアライズされたデータからオブジェクトが復元される際に、自動的に呼び出されます。一般的なクラスでは、__wakeupメソッドはデータベース接続の再確立など、オブジェクトが復元された後に必要な初期化処理を実装するために利用されます。しかし、RuntimeExceptionクラスおよびその親クラスであるExceptionでは、セキュリティ上の理由から例外オブジェクト自体のデシリアライズが禁止されています。これは、例外オブジェクトが持つスタックトレースなどの機密情報が、意図しない形で復元・利用されることを防ぐためです。そのため、RuntimeExceptionの__wakeupメソッドは、呼び出されると例外をスローするように実装されており、デシリアライズ処理を強制的に中断させます。この挙動により、アプリケーションの安全性が確保されます。
構文(syntax)
1<?php 2 3// RuntimeException::__wakeup() は、セキュリティ上の理由から 4// デシリアライズを防止するために final として宣言されており、 5// 実行しようとすると例外をスローします。 6 7$exception = new RuntimeException('This is a test.'); 8$serializedData = serialize($exception); 9 10try { 11 unserialize($serializedData); 12} catch (Exception $e) { 13 // "Unserialization of 'RuntimeException' is not allowed" というメッセージが出力されます。 14 echo $e->getMessage(); 15}
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP8: __wakeup バイパス脆弱性修正確認
1<?php 2 3/** 4 * __wakeup() メソッドの動作を検証するためのクラス 5 * 6 * PHPの古いバージョンには、デシリアライズ時にプロパティ数を偽装することで 7 * __wakeup() の呼び出しを回避できる脆弱性 (CVE-2016-7124) がありました。 8 * このサンプルコードは、現在のPHP (PHP 8) ではその脆弱性が修正されており、 9 * __wakeup() が正しく呼び出されることを示します。 10 */ 11class User 12{ 13 public string $username; 14 public bool $isAdmin = false; 15 16 public function __construct(string $username) 17 { 18 $this->username = $username; 19 echo "コンストラクタ: User '{$this->username}' が作成されました。(isAdmin: " . ($this->isAdmin ? 'true' : 'false') . ")\n"; 20 } 21 22 /** 23 * unserialize() の際に自動的に呼び出されるマジックメソッド 24 * 25 * オブジェクトの状態を安全に復元(再初期化)する目的で使われます。 26 * ここでは、セキュリティ上の理由から isAdmin プロパティを強制的に false に戻します。 27 */ 28 public function __wakeup(): void 29 { 30 echo "__wakeup: メソッドが呼び出されました。\n"; 31 // デシリアライズ時に管理者権限が不正に与えられることを防ぐ 32 $this->isAdmin = false; 33 echo "__wakeup: isAdmin プロパティを false にリセットしました。\n"; 34 } 35} 36 37// 1. 通常のオブジェクトを生成し、シリアライズする 38echo "--- Step 1: オブジェクトの生成とシリアライズ ---\n"; 39$user = new User('guest'); 40$serializedUser = serialize($user); 41echo "元のシリアライズ文字列: " . $serializedUser . "\n\n"; 42 43// 2. 脆弱性を悪用するため、シリアライズされた文字列を意図的に改ざんする 44// - isAdmin プロパティを true に設定する 45// - 古いPHPでのバイパス手法: プロパティ数 (2) を宣言よりも大きな値 (3) に変更する 46echo "--- Step 2: シリアライズ文字列の改ざん ---\n"; 47// O:4:"User":2:{s:8:"username";s:5:"guest";s:7:"isAdmin";b:0;} 48// ↓ isAdmin を true (b:1) にし、プロパティ数を 2 から 3 に偽装 49$maliciousPayload = 'O:4:"User":3:{s:8:"username";s:5:"guest";s:7:"isAdmin";b:1;}'; 50echo "改ざんされたペイロード: " . $maliciousPayload . "\n\n"; 51 52// 3. 改ざんされた文字列をデシリアライズする 53echo "--- Step 3: 改ざんされたペイロードのデシリアライズ ---\n"; 54// PHP 8 では、プロパティ数が一致しなくても __wakeup() は正しく呼び出される 55$restoredUser = unserialize($maliciousPayload); 56 57echo "\n--- Step 4: 結果の確認 ---\n"; 58if ($restoredUser instanceof User) { 59 echo "デシリアライズ成功。\n"; 60 echo "復元されたユーザー名: " . $restoredUser->username . "\n"; 61 echo "復元された isAdmin: " . ($restoredUser->isAdmin ? 'true' : 'false') . "\n"; 62 if (!$restoredUser->isAdmin) { 63 echo "結果: __wakeup() がバイパスされず、isAdmin プロパティが安全にリセットされました。\n"; 64 } else { 65 echo "結果: __wakeup() がバイパスされました。(このメッセージは現代のPHPでは表示されません)\n"; 66 } 67} else { 68 echo "デシリアライズに失敗しました。\n"; 69} 70
__wakeupは、unserialize()関数によって文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。このメソッドは引数を受け取らず、戻り値もありません。オブジェクトが復元された直後に、プロパティを検証したり、必要な初期化処理を行ったりする目的で使用されます。
このサンプルコードは、__wakeupメソッドを利用したセキュリティ対策の実例を示しています。まず、ユーザー情報を格納したオブジェクトをserialize()関数で一度文字列に変換します。次に、その文字列データを意図的に改ざんし、管理者権限(isAdminプロパティ)を不正に有効にしようと試みます。これは、過去のPHPに存在した脆弱性を悪用しようとする攻撃のシミュレーションです。
しかし、改ざんされた文字列をunserialize()関数でオブジェクトに戻す際、__wakeupメソッドが自動で実行されます。このメソッド内では、セキュリティ上の理由からisAdminプロパティを強制的にfalseに戻す処理が記述されています。
その結果、たとえ元のデータが改ざんされていても、__wakeupメソッドによってオブジェクトは安全な状態にリセットされます。このコードは、現在のPHPでは脆弱性が修正されており、デシリアライズ処理における__wakeupの安全機構が正しく機能することを証明しています。
__wakeupメソッドは、unserialize関数でオブジェクトが復元される際に自動で呼び出される特殊なメソッドです。この仕組みは、オブジェクトの状態を安全な形に初期化するために利用されます。このサンプルコードは、過去のPHPに存在した、シリアライズ文字列のプロパティ数を偽装して__wakeupの呼び出しを回避する脆弱性が、PHP 8では修正済みであることを示しています。これにより、改ざんされたデータからオブジェクトが復元されても、isAdminプロパティが強制的にfalseにリセットされ、意図しない権限昇格を防いでいます。外部から受け取ったデータをunserializeすることは本質的に危険なため、常に注意深い取り扱いが求められます。
PHP __wakeup マジックメソッドで再接続する
1<?php 2 3/** 4 * __wakeupマジックメソッドの使用例を示すクラス 5 * 6 * このクラスは、オブジェクトがデシリアライズ(unserialize)される際に 7 * 自動的に呼び出される __wakeup メソッドの動作をデモンストレーションします。 8 * データベース接続のような、シリアライズできないリソースを再初期化する際に役立ちます。 9 */ 10class Connection 11{ 12 /** @var string 接続情報 */ 13 private string $dsn; 14 15 /** @var ?string 接続リソース(シミュレーション) */ 16 private ?string $resource = null; 17 18 /** 19 * コンストラクタ 20 * 21 * @param string $dsn 接続情報 22 */ 23 public function __construct(string $dsn) 24 { 25 $this->dsn = $dsn; 26 $this->connect(); 27 } 28 29 /** 30 * オブジェクトがシリアライズされる際に呼び出される 31 * 32 * 保存したいプロパティ名の配列を返す必要があります。 33 * ここでは、接続リソース($resource)は含めず、接続情報($dsn)のみを保存します。 34 * 35 * @return string[] 36 */ 37 public function __sleep(): array 38 { 39 echo '__sleep が呼び出されました。接続を閉じます。' . PHP_EOL; 40 $this->resource = null; 41 return ['dsn']; 42 } 43 44 /** 45 * オブジェクトがデシリアライズ(復元)される際に呼び出される 46 * 47 * このメソッドで、失われた接続などのリソースを再確立します。 48 */ 49 public function __wakeup(): void 50 { 51 echo '__wakeup が呼び出されました。再接続します。' . PHP_EOL; 52 $this->connect(); 53 } 54 55 /** 56 * 接続を確立する(シミュレーション) 57 */ 58 private function connect(): void 59 { 60 echo "{$this->dsn} に接続しています..." . PHP_EOL; 61 $this->resource = '接続済み'; 62 } 63 64 /** 65 * 接続状態を表示する 66 */ 67 public function getStatus(): void 68 { 69 echo "現在の状態: " . ($this->resource ?? '未接続') . PHP_EOL; 70 } 71} 72 73// 1. オブジェクトを生成(__construct が呼ばれる) 74echo '--- オブジェクト生成 ---' . PHP_EOL; 75$connection = new Connection('mysql:host=localhost;dbname=test'); 76$connection->getStatus(); 77echo PHP_EOL; 78 79// 2. オブジェクトをシリアライズ(__sleep が呼ばれる) 80echo '--- シリアライズ ---' . PHP_EOL; 81$serializedObject = serialize($connection); 82echo "シリアライズされたデータ: {$serializedObject}" . PHP_EOL; 83echo PHP_EOL; 84 85// 3. シリアライズされたデータからオブジェクトを復元(__wakeup が呼ばれる) 86echo '--- デシリアライズ ---' . PHP_EOL; 87$unserializedConnection = unserialize($serializedObject); 88$unserializedConnection->getStatus(); 89 90?>
PHPの__wakeupは、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。主な目的は、データベース接続やファイルハンドルなど、シリアライズの過程で失われてしまうリソースを再初期化することです。
サンプルコードでは、データベース接続を模倣したConnectionクラスでその動作を示しています。まず、serialize()関数でオブジェクトを文字列に変換する際、対になる__sleep()メソッドが実行され、接続リソースは破棄されます。そして、保存された文字列からunserialize()関数でオブジェクトを復元する時に、__wakeup()メソッドが自動的に呼び出されます。このメソッド内で、保存しておいた接続情報(dsn)を使って再度接続処理を行うことで、オブジェクトは再び利用可能な状態に復元されます。
このように__wakeupは、オブジェクトが「目覚める」タイミングで必要な処理を定義するために使われます。このメソッドは引数を受け取らず、戻り値もありません。
__wakeupは、unserialize()関数によって文字列からオブジェクトが復元される際に自動的に呼び出される特殊なメソッドです。オブジェクト生成時に一度だけ呼ばれる__constructとは役割が異なります。このメソッドは、データベース接続やファイルハンドルなど、シリアライズ(保存)できないリソースを再初期化する目的で利用されます。多くの場合、シリアライズ時に呼ばれる__sleepメソッドと対で使われ、__sleepで接続などを閉じ、__wakeupで再接続する、という流れで実装します。注意点として、信頼できない外部からのデータをunserialize()で復元すると、脆弱性の原因となる可能性があります。必ず信頼できるデータに対してのみ使用してください。