【PHP8.x】LengthException::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、オブジェクトのデシリアライズを意図的に失敗させる処理を実行するメソッドです。PHPにおいて、__wakeupはunserialize()関数が実行された際に自動的に呼び出されるマジックメソッドで、通常はデータベース接続の再確立など、オブジェクトが復元された後の初期化処理を定義するために使用されます。しかし、LengthExceptionクラスおよびその親クラスであるExceptionでは、このメソッドはセキュリティ上の理由から異なる役割を持ちます。例外オブジェクトのデシリアライズは、予期せぬ挙動や脆弱性を引き起こす可能性があるため、原則として許可されていません。そのため、シリアライズされたLengthExceptionオブジェクトをunserialize()関数で復元しようとすると、この__wakeupメソッドがExceptionをスローし、処理を強制的に失敗させます。これにより、開発者が誤って例外オブジェクトをデシリアライズすることを防ぎ、アプリケーションの安全性を確保する仕組みとなっています。
構文(syntax)
1<?php 2 3class MyLengthException extends LengthException 4{ 5 public function __wakeup(): void 6 { 7 } 8}
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP __wakeup バイパス試行と結果
1<?php 2 3/** 4 * __wakeup の動作を検証するためのカスタム例外クラス。 5 * 6 * PHP 7.4.0以降、__wakeup() のバイパス脆弱性 (CVE-2016-7124) は修正されています。 7 * このサンプルコードは、現在のPHPバージョンでシリアライズされたオブジェクトのプロパティ数が 8 * 宣言と異なる場合に __wakeup() が呼び出されず、警告が発生することを示します。 9 */ 10class CustomWakeupException extends LengthException 11{ 12 private string $state; 13 14 public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) 15 { 16 parent::__construct($message, $code, $previous); 17 $this->state = 'initialized'; 18 echo "Constructor called. State: '{$this->state}'" . PHP_EOL; 19 } 20 21 /** 22 * オブジェクトがデシリアライズされるときに自動的に呼び出されます。 23 * リソースの再接続や状態の再初期化などに使用されます。 24 */ 25 public function __wakeup(): void 26 { 27 echo "__wakeup method called." . PHP_EOL; 28 $this->state = 're-initialized'; 29 } 30 31 public function getState(): string 32 { 33 return $this->state; 34 } 35} 36 37// 正常なシリアライズとデシリアライズの例 38echo "--- 1. Normal Unserialization ---" . PHP_EOL; 39$object = new CustomWakeupException('Normal object'); 40$serialized = serialize($object); 41echo "Serialized: " . $serialized . PHP_EOL; 42 43// unserialize() を呼び出すと __wakeup() が実行される 44/** @var CustomWakeupException $unserializedObject */ 45$unserializedObject = unserialize($serialized); 46echo "Unserialized object state: '{$unserializedObject->getState()}'" . PHP_EOL . PHP_EOL; 47 48 49// __wakeup バイパスの試行(現在のPHPでは失敗し、警告が発生します) 50echo "--- 2. Attempting __wakeup bypass (fails on modern PHP) ---" . PHP_EOL; 51 52// シリアライズされた文字列を手動で改ざんし、プロパティ数を 1 から 2 に増やす 53// これは、古いPHPバージョンでの __wakeup バイパス脆弱性の手法です 54$maliciousSerialized = str_replace('O:21:"CustomWakeupException":1:', 'O:21:"CustomWakeupException":2:', $serialized); 55echo "Maliciously modified serialized string: " . $maliciousSerialized . PHP_EOL; 56 57// 改ざんされた文字列をデシリアライズしようとすると、PHP 7.4.0以降では 58// __wakeup() は呼び出されずに警告が発生し、結果として false が返されます。 59$result = @unserialize($maliciousSerialized); // @で警告出力を抑制 60 61if ($result === false) { 62 echo "Unserialization failed as expected. __wakeup() was not bypassed." . PHP_EOL; 63} 64
__wakeupメソッドは、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に、自動的に呼び出される特殊なメソッドです。データベース接続の再確立など、オブジェクトが復元された後に行うべき初期化処理を定義するために利用されます。このメソッドは引数を受け取らず、何も値を返しません。
サンプルコードは、__wakeupの動作と、過去に存在した脆弱性について示しています。前半の正常な処理では、CustomWakeupExceptionオブジェクトをserialize()で文字列化し、unserialize()で復元しています。この時、__wakeupメソッドが自動で実行され、オブジェクトのプロパティが再初期化されることがわかります。
後半では、シリアライズされた文字列のプロパティ数を意図的に改ざんし、__wakeupメソッドの実行を回避しようと試みています。これは古いPHPバージョンに存在した脆弱性を利用した手法ですが、現在のPHPではこの問題は修正されています。そのため、改ざんされた文字列をunserialize()しようとすると処理は失敗し、__wakeupが呼び出されることはありません。この挙動により、現在のPHPがより安全になっていることを確認できます。
__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動的に呼ばれる特殊なメソッドで、リソースの再接続などの初期化処理に使われます。最も重要な注意点は、unserialize()に外部から受け取った信頼できないデータを渡すと、深刻なセキュリティリスクにつながる可能性があることです。サンプルコードは、過去に存在した__wakeupの実行を回避する脆弱性(CVE-2016-7124)を再現しようとしています。しかし、現在のPHPではこの脆弱性は修正されており、シリアライズデータ内のプロパティ数と実際の定義が異なるとunserialize()は失敗し、__wakeupは実行されません。このことから、信頼できないデータを扱う場合はunserialize()の使用を避け、より安全なjson_decodeなどを検討することが推奨されます。
PHP __wakeup() マジックメソッドでオブジェクトを復元する
1<?php 2 3/** 4 * __wakeup()マジックメソッドの使用法を示すクラス 5 * 6 * このメソッドは、オブジェクトが unserialize() によって復元される際に自動的に呼び出されます。 7 * データベース接続の再確立など、オブジェクトの初期化処理に利用されます。 8 */ 9class UserSession 10{ 11 public string $username; 12 public DateTime $createdAt; 13 // データベース接続のような、シリアライズできないリソースを想定 14 private mixed $dbConnection = null; 15 16 public function __construct(string $username) 17 { 18 $this->username = $username; 19 $this->createdAt = new DateTime(); 20 $this->connect(); 21 } 22 23 /** 24 * オブジェクトのシリアライズ(文字列化)の直前に呼び出される 25 * 26 * シリアライズしたいプロパティ名の配列を返す必要があります。 27 * ここでは、接続リソースである $dbConnection を除外しています。 28 * @return array<string> 29 */ 30 public function __sleep(): array 31 { 32 echo "マジックメソッド __sleep() が呼び出されました。\n"; 33 return ['username', 'createdAt']; 34 } 35 36 /** 37 * オブジェクトのデシリアライズ(復元)の直後に呼び出される 38 * 39 * このメソッドで、シリアライズ時に失われたデータベース接続などを再確立します。 40 * 引数や戻り値はありません。 41 */ 42 public function __wakeup(): void 43 { 44 echo "マジックメソッド __wakeup() が呼び出されました。\n"; 45 // データベース接続を再確立する 46 $this->connect(); 47 } 48 49 // データベースへの接続をシミュレートするメソッド 50 private function connect(): void 51 { 52 echo "データベースに接続しています...\n"; 53 // 実際のコードではここでデータベース接続処理を行う 54 $this->dbConnection = 'Connected'; 55 } 56 57 // 接続状態を確認するためのメソッド 58 public function getConnectionStatus(): string 59 { 60 return $this->dbConnection === 'Connected' ? '接続済み' : '未接続'; 61 } 62} 63 64// 1. 新しいオブジェクトを作成します 65echo "--- オブジェクトの作成 ---\n"; 66$session = new UserSession('alice'); 67echo "ユーザー '{$session->username}' のセッションを作成しました。\n"; 68echo "接続状態: " . $session->getConnectionStatus() . "\n\n"; 69 70// 2. オブジェクトをシリアライズします。__sleep() が呼び出されます。 71echo "--- オブジェクトのシリアライズ ---\n"; 72$serializedSession = serialize($session); 73echo "シリアライズ後のデータ: {$serializedSession}\n\n"; 74 75// シリアライズされた後、オブジェクトの参照を解除します 76unset($session); 77 78// 3. データをデシリアライズしてオブジェクトを復元します。__wakeup() が呼び出されます。 79echo "--- オブジェクトのデシリアライズ ---\n"; 80$unserializedSession = unserialize($serializedSession); 81 82echo "ユーザー '{$unserializedSession->username}' のセッションを復元しました。\n"; 83echo "接続状態: " . $unserializedSession->getConnectionStatus() . "\n";
PHPの__wakeup()は、unserialize()関数によってオブジェクトが復元される際に自動的に呼び出される「マジックメソッド」です。このメソッドは、オブジェクトがシリアライズ(文字列化)される過程で失われたリソースを再初期化する目的で利用されます。
サンプルコードのUserSessionクラスでは、この仕組みが分かりやすく示されています。まず、serialize()関数でオブジェクトを文字列化する際、対になる__sleep()メソッドが呼び出され、データベース接続のような保存できないリソース($dbConnection)は除外されます。その後、unserialize()関数で文字列からオブジェクトを復元する際に、__wakeup()メソッドが自動的に実行されます。
このサンプルでは__wakeup()メソッド内でconnect()メソッドを呼び出し、失われていたデータベース接続を再確立しています。これにより、復元されたオブジェクトは、作成時と同様に正常に機能できる状態になります。__wakeup()メソッドは引数を受け取らず、戻り値もありません。その役割は、オブジェクトが復元された直後に必要な初期化処理を実行することにあります。このように、__wakeup()はオブジェクトの状態を復元する上で重要な役割を担います。
__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動的に呼び出される特殊なメソッドで、自分で直接呼び出すものではありません。主な目的は、データベース接続など、serialize()で文字列として保存できないリソースを再確立することです。多くの場合、シリアライズ時に呼ばれる__sleepメソッドと対で利用され、__sleepで除外したプロパティを__wakeupで復元します。最も重要な注意点はセキュリティです。信頼できない文字列をunserialize()に渡すと、この__wakeupメソッドが意図せず実行され、深刻な脆弱性を引き起こす可能性があります。unserialize()を使用する際は、入力データが必ず信頼できるものであることを確認してください。