【PHP8.x】DomainException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、シリアル化されたDomainExceptionオブジェクトがアンシリアライズされることを防ぐために、意図的に例外をスローするメソッドです。このメソッドはPHPのマジックメソッドの一つであり、unserialize()関数によってオブジェクトのデータが復元された直後に自動的に呼び出されます。PHP 8では、セキュリティ強化の一環として、Exceptionクラスおよびその全てのサブクラス(DomainExceptionを含む)のアンシリアライズが禁止されています。これは、悪意を持って巧妙に作成されたシリアル化データを介して、予期しないコードが実行されるオブジェクトインジェクション脆弱性を防ぐための重要な措置です。したがって、シリアル化されたDomainExceptionオブジェクトをunserialize()で復元しようとすると、この__wakeupメソッドが実行され、常に新たなExceptionがスローされることで処理が失敗します。開発者がこのメソッドを直接呼び出すことは通常なく、PHPのシリアライゼーション機構の一部として、システムの安全性を確保する役割を担っています。
構文(syntax)
1final public function __wakeup(): void {}
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP __wakeup バイパス対策の確認
1<?php 2 3/** 4 * __wakeup() マジックメソッドのバイパス脆弱性(CVE-2016-7124)が 5 * 現在のPHPバージョンでは対策済みであることを示すためのクラスです。 6 * 7 * この脆弱性は、シリアライズされた文字列内のプロパティ数を不正に増やすことで、 8 * unserialize()時に __wakeup() の呼び出しをスキップできてしまうというものでした。 9 * PHP 7.1以降ではこの問題は修正されています。 10 */ 11class WakeupBypassDemo 12{ 13 // オブジェクトのプロパティ 14 public string $data = 'default data'; 15 16 /** 17 * オブジェクトが unserialize() されるときに自動的に呼び出されるマジックメソッド。 18 * このメソッドが呼び出されたことを明確にするためにメッセージを出力します。 19 */ 20 public function __wakeup(): void 21 { 22 echo "__wakeup() メソッドが呼び出されました。\n"; 23 // ここでリソースの再接続などの初期化処理を行うことが意図されています。 24 $this->data = 'woken up!'; 25 } 26} 27 28// 1. オブジェクトを生成し、シリアライズ(文字列に変換)します。 29$obj = new WakeupBypassDemo(); 30$serialized_data = serialize($obj); 31 32echo "元のシリアライズ文字列:\n"; 33echo $serialized_data . "\n\n"; 34// 出力例: O:16:"WakeupBypassDemo":1:{s:4:"data";s:12:"default data";} 35// ~~~~~~~~~~ ~ <-- クラス名とプロパティ数(1) 36 37// 2. 脆弱性を悪用する試みとして、プロパティ数を意図的に増やします。 38// プロパティ数は 1 なのを 2 に書き換えます。 39$malicious_data = str_replace('O:16:"WakeupBypassDemo":1:', 'O:16:"WakeupBypassDemo":2:', $serialized_data); 40 41echo "改ざんされたシリアライズ文字列(プロパティ数を1から2に変更):\n"; 42echo $malicious_data . "\n\n"; 43 44// 3. 改ざんされた文字列を unserialize() します。 45echo "改ざんされた文字列で unserialize() を試みます...\n"; 46 47// PHP 7.1以降(PHP 8を含む)では、プロパティ数と実際のデータが一致しないため、 48// unserialize() は失敗し、falseを返して E_NOTICE エラーが発生します。 49// これにより、__wakeup() のバイパスは防止されます。 50$restored_obj = unserialize($malicious_data); 51 52if ($restored_obj === false) { 53 echo "unserialize() に失敗しました。オブジェクトは復元されませんでした。\n"; 54 echo "これは、__wakeup() のバイパス脆弱性が対策されていることを意味します。\n"; 55} else { 56 // 古いバージョンのPHPでは、このブロックが実行され、 57 // __wakeup()が呼び出されずにオブジェクトが不完全な状態で復元されてしまいました。 58 echo "オブジェクトが復元されました。\n"; 59 var_dump($restored_obj); 60}
__wakeupは、unserialize()関数によって文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。引数はなく、戻り値も返しません。オブジェクトが復元された後、データベース接続の再確立など、何らかの初期化処理を再開するために利用されます。
このサンプルコードは、過去に存在した__wakeupメソッドの実行を意図的に回避できてしまう脆弱性が、PHP 8では対策済みであることを示しています。この脆弱性は、serialize()で生成された文字列内のプロパティ数を、実際の数より大きな値に書き換えることで、unserialize()実行時に__wakeupメソッドの呼び出しをスキップさせるというものでした。
コードでは、オブジェクトを文字列化した後、プロパティ数を「1」から「2」へ不正に書き換えています。この改ざんされた文字列をunserialize()で復元しようとすると、現在のPHPではプロパティ数と実際のデータが一致しないため、処理は失敗しオブジェクトは生成されません。これにより、__wakeupメソッドの呼び出しが回避されるという脆弱性は防がれています。
__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動で呼び出される特殊なメソッドです。このサンプルコードは、過去のPHPに存在した__wakeupの実行を回避できてしまう脆弱性が、現在のPHP 8では修正済みであることを示しています。シリアライズされた文字列のプロパティ数を不正に書き換えても、現行バージョンではデータ不整合と判断され、unserialize()が失敗するため安全です。最も重要な注意点は、ユーザー入力など外部から受け取った信頼できない文字列をunserialize()で処理することは、他の脆弱性を突かれる可能性があるため非常に危険という点です。データのやり取りには、より安全なJSON形式(json_decode関数など)の使用を強く推奨します。
PHP DomainException::__wakeup() によるオブジェクト復元
1<?php 2 3/** 4 * DomainException をシリアライズし、アンシリアライズするサンプルコード。 5 * 6 * __wakeup() は、unserialize() 関数がオブジェクトを復元する際に 7 * 自動的に呼び出されるマジックメソッドです。 8 * DomainException クラスの __wakeup() メソッドは Exception クラスから継承されており、 9 * オブジェクトの状態を再構築する役割を担います。 10 * このコードは、シリアライズされた例外オブジェクトが unserialize() によって 11 * 正常に復元される様子を示します。その過程で __wakeup() が内部的に呼び出されます。 12 */ 13final class WakeupExample 14{ 15 /** 16 * サンプルコードを実行します。 17 */ 18 public static function run(): void 19 { 20 try { 21 // 確認用の DomainException をスローします。 22 throw new DomainException('入力値がドメインの範囲外です。', 123); 23 } catch (DomainException $e) { 24 echo '元の例外オブジェクト:' . PHP_EOL; 25 print_r($e->getMessage() . PHP_EOL); 26 27 // 例外オブジェクトをシリアライズ(文字列化)します。 28 $serialized = serialize($e); 29 echo PHP_EOL . 'シリアライズされたデータ:' . PHP_EOL; 30 print_r($serialized . PHP_EOL); 31 32 // 文字列からオブジェクトをアンシリアライズ(復元)します。 33 // この時、内部で DomainException::__wakeup() が呼び出されます。 34 $unserialized = unserialize($serialized); 35 36 echo PHP_EOL . '復元された例外オブジェクト:' . PHP_EOL; 37 print_r($unserialized->getMessage() . PHP_EOL); 38 39 // 復元されたオブジェクトが元のクラスのインスタンスであることを確認します。 40 if ($unserialized instanceof DomainException) { 41 echo PHP_EOL . 'オブジェクトは正常に DomainExceptionとして復元されました。' . PHP_EOL; 42 } 43 } 44 } 45} 46 47// サンプルコードを実行 48WakeupExample::run();
DomainExceptionクラスの__wakeup()メソッドは、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に、自動的に呼び出される特別なメソッド(マジックメソッド)です。このメソッドの主な役割は、オブジェクトの状態を再構築することです。DomainExceptionは、親クラスであるExceptionクラスから__wakeup()メソッドを継承しています。
このサンプルコードは、まずDomainExceptionオブジェクトを作成し、serialize()関数で保存や転送が可能な文字列データに変換しています。次に、その文字列データをunserialize()関数で元のオブジェクトに復元します。このunserialize()が実行されるタイミングで、PHPの内部では__wakeup()メソッドが自動的に呼び出され、オブジェクトが正しく機能するように準備を整えます。その結果、例外メッセージなどの情報が保持された状態でオブジェクトが復元されることを示しています。
__wakeup()メソッドは、呼び出される際に引数を必要とせず、また、処理後に値を返すこともありません。その目的は、何かを返すことではなく、オブジェクト自体の内部状態を正しく復元することに特化しているためです。
__wakeup()は、unserialize()関数でオブジェクトを復元する際に自動で呼び出される特殊なメソッドです。主な用途は、データベース接続の再確立など、復元後に必要な初期化処理です。最も重要な注意点は、unserialize()関数にセキュリティ上のリスクがあることです。信頼できない外部から受け取った文字列を安易にunserialize()すると、意図しないコードが実行される脆弱性の原因となる可能性があります。この関数に渡すデータは、必ず自分で生成したものか、信頼できるソースからのものに限定してください。また、オブジェクトの復元時にはコンストラクタ(__construct)が呼ばれないため、__wakeup()がその後の初期化処理を担う点を理解しておくことが大切です。