【PHP8.x】BadMethodCallException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
__wakeupメソッドは、BadMethodCallExceptionオブジェクトのデシリアライズを防止するメソッドです。PHPには、オブジェクトの状態を保存可能な文字列形式に変換する「シリアライズ」と、その文字列からオブジェクトを復元する「デシリアライズ」という機能があります。一般的に__wakeupマジックメソッドは、unserialize()関数によってオブジェクトがデシリアライズされる際に自動的に呼び出され、データベース接続の再確立など、復元後に必要な初期化処理を行うために使われます。しかし、BadMethodCallExceptionを含むPHPの標準的な例外クラスでは、セキュリティ上の理由からこの挙動が異なります。シリアライズされた例外オブジェクトを悪用した攻撃を防ぐため、これらのクラスのデシリアライズは意図的に禁止されています。もし、シリアライズされたBadMethodCallExceptionオブジェクトをunserialize()で復元しようとすると、この__wakeupメソッドが呼び出される代わりに例外がスローされ、処理が失敗します。これにより、アプリケーションの安全性が確保されるため、開発者がこのメソッドを直接呼び出したり、独自の処理を実装したりすることは通常ありません。
構文(syntax)
1<?php 2 3// final public __wakeup(): void 4
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP __wakeup バイパス試行
1<?php 2 3declare(strict_types=1); 4 5/** 6 * __wakeup() のバイパス脆弱性(CVE-2016-7124)の概念を説明するためのクラスです。 7 * この脆弱性は古いPHPバージョンに存在しましたが、PHP 8 では修正されています。 8 */ 9class UnserializableObject 10{ 11 public ?string $message = null; 12 13 /** 14 * unserialize() が呼び出された際に自動的に実行されるマジックメソッド。 15 * このクラスでは、セキュリティ上の理由から unserialize を意図的に禁止します。 16 * 17 * @throws BadMethodCallException 常に例外をスローします。 18 */ 19 public function __wakeup(): void 20 { 21 // unserializeを試みた際に、BadMethodCallExceptionをスローして処理を中断させる 22 throw new BadMethodCallException('このオブジェクトのデシリアライズは許可されていません。'); 23 } 24} 25 26// 正常なオブジェクトをシリアライズします 27$object = new UnserializableObject(); 28$object->message = 'これは秘密のメッセージです。'; 29$serializedString = serialize($object); 30 31echo '元のシリアライズ文字列: ' . $serializedString . PHP_EOL; 32// 出力例: O:20:"UnserializableObject":1:{s:7:"message";s:33:"これは秘密のメッセージです。";} 33 34// __wakeup() のバイパスを試みるために、シリアライズ文字列を改ざんします。 35// 古いPHPでは、宣言されたプロパティ数と実際のプロパティ数が異なると__wakeup()がスキップされました。 36// ここではプロパティ数を '1' から '2' に書き換えています。 37$maliciousString = preg_replace('/^O:(\d+):"([^"]+)":1:/', 'O:$1:"$2":2:', $serializedString); 38 39echo '改ざん後のシリアライズ文字列: ' . $maliciousString . PHP_EOL; 40 41echo PHP_EOL . '改ざんされた文字列のデシリアライズを試みます...' . PHP_EOL; 42 43try { 44 // 改ざんされた文字列をデシリアライズしようとします 45 unserialize($maliciousString); 46} catch (BadMethodCallException $e) { 47 // PHP 8 では脆弱性が修正されているため、バイパスは成功しません。 48 // その結果、__wakeup() が正しく呼び出され、意図通りに例外がスローされます。 49 echo '成功: ' . $e->getMessage() . PHP_EOL; 50} catch (Throwable $e) { 51 // 予期せぬエラー 52 echo 'エラー: ' . $e->getMessage() . PHP_EOL; 53}
__wakeupは、unserialize()関数によって文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。このメソッドは引数を受け取らず、戻り値もありません。オブジェクトが復元された直後に必要な初期化処理などを定義するために使用されます。
このサンプルコードは、__wakeupメソッドの本来の用途を応用し、セキュリティ上の理由からオブジェクトのデシリアライズ(復元)を意図的に禁止する実装例を示しています。UnserializableObjectクラスでは、__wakeupメソッドが呼び出されると、必ずBadMethodCallExceptionという例外を発生させるように作られています。これにより、このオブジェクトをデシリアライズしようとすると処理が強制的に中断されます。
また、このコードは古いPHPバージョンに存在したセキュリティ脆弱性(CVE-2016-7124)にも触れています。以前は、シリアライズされた文字列内のプロパティ数を不正に改ざんすることで、__wakeupメソッドの実行自体を回避できてしまう問題がありました。しかし、PHP 8ではこの脆弱性は修正済みです。そのため、サンプルコードのように改ざんされた文字列をデシリアライズしようとしても、__wakeupメソッドは正しく実行され、意図通りに例外が発生して処理が安全に失敗します。
このコードは、信頼できない文字列を安易に unserialize() することの危険性を示しています。__wakeup はデシリアライズ時に自動実行されるメソッドで、ここではセキュリティチェックのために意図的に例外を発生させています。サンプルでは、シリアライズされた文字列を改ざんし、この __wakeup の呼び出しを回避する攻撃を試みています。これは古いPHPバージョンに存在した脆弱性ですが、PHP 8ではすでに修正されています。そのため、このコードをPHP 8で実行すると、脆弱性は機能せず、__wakeup が正しく呼び出され、意図通り例外が発生します。安全な開発のため、常に最新のPHPバージョンを利用し、信頼できないデータのデシリアライズは避けることが重要です。
PHP __wakeup マジックメソッドでオブジェクトを復元する
1<?php 2 3/** 4 * __wakeup マジックメソッドの使用例を示すクラス 5 * 6 * このクラスは、オブジェクトが unserialize() によって復元される際に、 7 * __wakeup() メソッドが自動的に呼び出されることを示します。 8 */ 9class Connection 10{ 11 /** @var string|null 接続状態を表すプロパティ */ 12 public ?string $status = null; 13 14 /** 15 * コンストラクタ 16 * オブジェクト生成時に接続状態を初期化します。 17 */ 18 public function __construct() 19 { 20 $this->status = 'Connected'; 21 echo 'Connection object created. Status: ' . $this->status . PHP_EOL; 22 } 23 24 /** 25 * serialize() 時に呼び出されるマジックメソッド 26 * 保存するプロパティの配列を返します。 27 * 28 * @return string[] 29 */ 30 public function __sleep(): array 31 { 32 echo '__sleep called: Preparing to serialize.' . PHP_EOL; 33 // status プロパティのみをシリアライズ対象とする 34 return ['status']; 35 } 36 37 /** 38 * unserialize() 時に呼び出されるマジックメソッド 39 * 40 * オブジェクトの復元時に、リソースの再接続や初期化処理を行うために使用されます。 41 * ここでは、接続状態を更新しています。 42 */ 43 public function __wakeup(): void 44 { 45 $this->status = 'Reconnected after unserialize'; 46 echo '__wakeup called: Object has been restored. Status: ' . $this->status . PHP_EOL; 47 } 48} 49 50// 1. Connectionクラスのインスタンスを生成 51$connection = new Connection(); 52echo '--------------------' . PHP_EOL; 53 54// 2. オブジェクトをシリアライズ(文字列に変換) 55// この過程で __sleep() が呼び出される 56$serializedConnection = serialize($connection); 57echo 'Serialized data: ' . $serializedConnection . PHP_EOL; 58echo '--------------------' . PHP_EOL; 59 60// 3. シリアライズされた文字列からオブジェクトを復元 61// この過程で __wakeup() が自動的に呼び出される 62$unserializedConnection = unserialize($serializedConnection); 63 64// 復元されたオブジェクトのプロパティを確認 65// __wakeup() によって status が 'Reconnected after unserialize' に変更されていることがわかる 66// var_dump($unserializedConnection); 67 68?>
__wakeupメソッドは、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に、自動的に呼び出されるPHPの「マジックメソッド」です。マジックメソッドとは、特定の状況でPHPが自動的に実行する特別なメソッドのことを指します。
このメソッドの主な目的は、オブジェクトの復元時に必要な初期化処理を行うことです。例えば、オブジェクトがデータベース接続情報を持っている場合、serialize()で保存する際に接続は切断されます。その後unserialize()で復元した際に、この__wakeup()メソッド内で再度データベースに接続する処理などを記述します。
サンプルコードでは、Connectionクラスのインスタンスをserialize()で文字列に変換し、その後unserialize()でオブジェクトに復元しています。この復元の過程で__wakeup()メソッドが自動的に実行され、statusプロパティの値を「Reconnected after unserialize」という文字列に更新しています。これにより、オブジェクトが復元されたことを示す状態に変更されます。
__wakeupメソッドは引数を受け取らず、また、戻り値も返しません。その役割は、オブジェクト自身の状態を整えることに特化しています。
__wakeupメソッドは、unserialize()関数で文字列からオブジェクトを復元する際に自動的に呼び出されます。サンプルコードのように、データベース接続の再確立や、オブジェクトのプロパティを初期化する処理に適しています。注意すべきはセキュリティ面です。信頼できない外部からの文字列をunserialize()で復元すると、意図しないコードが実行される脆弱性(PHP Object Injection)の原因となる可能性があります。そのため、この関数に渡すデータは、必ず信頼できるソースからのものに限定してください。__wakeupは、オブジェクトを文字列に変換するserialize()時に呼ばれる__sleepメソッドと対で理解すると分かりやすいです。