【PHP8.x】OutOfBoundsException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
__wakeupメソッドは、unserialize関数によってオブジェクトが復元される際に、自動的に呼び出される処理を実行するメソッドです。これはPHPの「マジックメソッド」と呼ばれる特殊なメソッドの一つで、オブジェクトがシリアライズされた状態から元に戻される(デシリアライズされる)ときに、必要な初期化処理を行うために設計されています。例えば、データベース接続の再確立などが典型的な用途です。しかし、OutOfBoundsExceptionクラスは、PHPの基本的な例外クラスであるExceptionクラスを継承しています。セキュリティ上の脆弱性を防ぐため、PHPではExceptionクラスおよびその派生クラスのオブジェクトをデシリアライズすることは許可されていません。もしシリアライズされたOutOfBoundsExceptionオブジェクトをunserialize関数で復元しようとすると、__wakeupメソッドがエラーを発生させ、処理を中断します。この仕組みにより、悪意のあるデータから例外オブジェクトが再構築され、予期せぬ動作を引き起こすことを防いでいます。したがって、このメソッドは開発者が直接利用するものではなく、PHPのセキュリティ機構の一部として機能します。
構文(syntax)
1<?php 2 3final public function __wakeup(): void 4{ 5}
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP __wakeup bypass 脆弱性再現
1<?php 2 3/** 4 * __wakeup() の動作と、過去に存在したバイパス脆弱性を説明するためのクラスです。 5 * 6 * __wakeup() は unserialize() が実行された際に自動的に呼び出されるマジックメソッドで、 7 * オブジェクトの状態を復元・初期化する目的で使用されます。 8 */ 9class User 10{ 11 public string $username; 12 public bool $isAdmin = false; 13 14 /** 15 * デシリアライズ時に呼び出され、オブジェクトを初期化します。 16 * 例えば、このオブジェクトが管理者権限を持つべきではないことを保証します。 17 */ 18 public function __wakeup(): void 19 { 20 echo "__wakeup() has been called.\n"; 21 // デシリアライズ後、管理者フラグを強制的に false にリセットする 22 $this->isAdmin = false; 23 echo "isAdmin property has been reset to false.\n\n"; 24 } 25} 26 27echo "Step 1: Create a normal user object and serialize it.\n"; 28$user = new User(); 29$user->username = 'guest'; 30$user->isAdmin = false; // 初期状態は管理者ではない 31 32$serializedUser = serialize($user); 33echo "Serialized string: " . $serializedUser . "\n\n"; 34 35// --- __wakeup() bypassの試行 --- 36// CVE-2016-7124として知られた古いPHPの脆弱性では、シリアライズされた文字列の 37// プロパティ数を実際の数よりも多く改ざんすることで __wakeup() の呼び出しを 38// スキップできました。 39 40echo "Step 2: Attempt to bypass __wakeup() by manipulating the serialized string.\n"; 41// O:4:"User":2:{...} の "2" (プロパティ数) を "3" に改ざんします。 42$maliciousPayload = str_replace('O:4:"User":2:', 'O:4:"User":3:', $serializedUser); 43// さらに、不正に管理者権限を true に設定します。 44$maliciousPayload = str_replace('b:0;', 'b:1;', $maliciousPayload); 45 46echo "Malicious payload: " . $maliciousPayload . "\n\n"; 47 48 49echo "Step 3: Try to unserialize the malicious payload.\n"; 50// PHP 8ではこの脆弱性は修正されています。 51// 不正なデータでデシリアライズを試みると、__wakeup() は呼び出されず、 52// unserialize() は false を返し、警告(Warning)が発生します。 53// これにより、意図しないプロパティの書き換えや初期化処理のスキップが防がれます。 54$deserializedObject = unserialize($maliciousPayload); 55 56if ($deserializedObject === false) { 57 echo "Result: Deserialization failed as expected in PHP 8.\n"; 58 echo "The __wakeup() method was NOT called, and the bypass attempt was successfully prevented.\n"; 59} else { 60 // このブロックは脆弱性のある古いPHPバージョンでのみ実行されます。 61 echo "Result: Deserialization was successful (This should not happen in modern PHP).\n"; 62 var_dump($deserializedObject); 63} 64
このPHPサンプルコードは、unserialize()関数が実行される際に自動的に呼び出されるマジックメソッド__wakeup()の役割と、過去に存在したセキュリティ脆弱性について解説するものです。__wakeup()メソッドは引数を取らず、戻り値もありません。主な目的は、シリアライズされたデータからオブジェクトが復元された際に、その状態を正しく初期化することです。
コード内のUserクラスでは、__wakeup()メソッドがオブジェクトのisAdminプロパティを強制的にfalseにリセットする処理を定義しています。これは、デシリアライズ時に意図せず管理者権限が有効になることを防ぐための安全策です。
次に、この__wakeup()メソッドの呼び出しを意図的に回避しようとする「バイパス攻撃」を試みています。過去のPHPバージョンには、シリアライズされた文字列内のプロパティ数を改ざんすることで__wakeup()の実行をスキップできてしまう脆弱性(CVE-2016-7124)がありました。しかし、このサンプルコードをPHP 8で実行すると、改ざんされたデータのデシリアライズは失敗し、unserialize()はfalseを返します。これにより、脆弱性を利用した攻撃が防止され、__wakeup()による初期化処理が確実に保証されることが分かります。
__wakeup()は、unserialize()関数によってオブジェクトが復元される際に自動で呼び出される特殊なメソッドです。最も重要な注意点は、ユーザー入力のような信頼できないデータをunserialize()に渡してはならないことです。これは深刻なセキュリティ脆弱性につながる可能性があります。このサンプルコードで試みられている__wakeup()の呼び出しを回避する攻撃手法は、古いPHPの脆弱性を利用したもので、PHP 8では既に対策されています。現在のPHPでは不正なデータを渡すとunserialize()は失敗しfalseを返すため、戻り値のチェックが重要です。__wakeup()は、デシリアライズ後のオブジェクトの状態を安全に初期化し直す目的で利用します。
PHP __wakeup メソッドで接続を再確立する
1<?php 2 3/** 4 * __wakeup マジックメソッドの使用例を示すクラス 5 * 6 * このクラスは、擬似的なリソース接続を管理します。 7 * オブジェクトがアンシリアライズされる際に、失われた接続を 8 * 自動的に再確立する処理を __wakeup メソッドに実装します。 9 */ 10class ResourceConnector 11{ 12 /** 13 * リソースの接続情報 (例: DSN) 14 * 15 * @var string 16 */ 17 private string $resourceInfo; 18 19 /** 20 * 接続状態を表すプロパティ 21 * シリアライズ時にこの状態は保存されません。 22 * 23 * @var ?string 24 */ 25 private ?string $connectionStatus = null; 26 27 /** 28 * コンストラクタ 29 * オブジェクト生成時に接続を初期化します。 30 * 31 * @param string $resourceInfo 接続情報 32 */ 33 public function __construct(string $resourceInfo) 34 { 35 $this->resourceInfo = $resourceInfo; 36 $this->connect(); 37 } 38 39 /** 40 * __wakeup マジックメソッド 41 * 42 * unserialize() 関数によってオブジェクトが復元される際に、 43 * PHP によって自動的に呼び出されます。 44 * ここでは、失われた接続を再確立する処理を行います。 45 */ 46 public function __wakeup(): void 47 { 48 echo "__wakeup() が呼び出されました。\n"; 49 $this->connect(); 50 } 51 52 /** 53 * 擬似的な接続処理 54 */ 55 private function connect(): void 56 { 57 $this->connectionStatus = "接続済み: " . $this->resourceInfo; 58 echo "リソースに接続しました。\n"; 59 } 60 61 /** 62 * 現在の接続状態を取得します。 63 * 64 * @return string 65 */ 66 public function getStatus(): string 67 { 68 return $this->connectionStatus ?? '未接続'; 69 } 70} 71 72// 1. オブジェクトを生成します。コンストラクタが呼ばれ、接続が確立されます。 73echo "--- オブジェクト生成 ---\n"; 74$connector = new ResourceConnector('database-main'); 75echo "生成後の状態: " . $connector->getStatus() . "\n\n"; 76 77// 2. オブジェクトをシリアライズ(文字列化)します。 78// これにより、オブジェクトの状態を保存したり転送したりできますが、 79// データベース接続のようなライブ接続は失われます。 80echo "--- シリアライズ ---\n"; 81$serializedObject = serialize($connector); 82echo "シリアライズされたデータ: " . $serializedObject . "\n\n"; 83 84// オブジェクトの参照を一旦破棄します 85unset($connector); 86 87// 3. シリアライズされた文字列からオブジェクトを復元(アンシリアライズ)します。 88// この処理の過程で __wakeup() メソッドが自動的に呼び出されます。 89echo "--- アンシリアライズ ---\n"; 90$restoredConnector = unserialize($serializedObject); 91 92// 4. 復元されたオブジェクトの状態を確認します。 93// __wakeup() によって接続が再確立されていることがわかります。 94echo "\n復元後の状態: " . $restoredConnector->getStatus() . "\n"; 95 96?>
__wakeupは、unserialize()関数によってオブジェクトが復元される際に、PHPが自動的に呼び出す特別なメソッド(マジックメソッド)です。このメソッドは、オブジェクトの状態を復元するために必要な初期化処理を定義する目的で使用されます。引数はなく、また、何も値を返す必要はありません(戻り値なし)。
サンプルコードでは、擬似的なリソース接続を管理するResourceConnectorクラスを定義しています。まず、serialize()関数でオブジェクトを文字列に変換します。この過程で、データベース接続のような動的なリソースは失われます。次に、unserialize()関数を使って、この文字列からオブジェクトを復元します。このとき、PHPは自動的に__wakeupメソッドを呼び出します。サンプルコードの__wakeupメソッド内では、失われた接続を再確立するためのconnect()メソッドが再度呼び出されています。これにより、復元されたオブジェクトは、シリアライズ前と同じように、再びリソースに接続された状態で利用できるようになります。
このように__wakeupは、シリアライズによって一時的に失われたリソースの再接続や、その他の初期化処理を行い、オブジェクトを完全に利用可能な状態に復帰させるために重要な役割を果たします。
__wakeupメソッドは、unserialize関数によって文字列からオブジェクトが復元される際に、自動的に呼び出される特殊なメソッドです。主な用途は、データベース接続やファイルハンドルなど、シリアライズの過程で失われてしまうリソースを再初期化することです。サンプルコードでは、この仕組みを利用して接続状態を復元しています。最も重要な注意点は、信頼できない外部データをunserializeしないことです。意図しないコードが実行される深刻なセキュリティ脆弱性の原因となる可能性があるため、入力値の検証は慎重に行ってください。__constructがオブジェクト新規作成時に呼ばれるのに対し、__wakeupは復元時に呼ばれるという役割の違いを理解することが大切です。