【PHP8.x】Dom\ProcessingInstruction::__wakeup()メソッドの使い方
__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
『__wakeupメソッドは、unserialize関数によってオブジェクトが復元される際に、そのオブジェクトの整合性を検証する処理を実行するメソッドです。』
PHPには、serialize関数を用いてオブジェクトを文字列へ変換し、セッションやファイルに保存可能にする機能があります。そしてunserialize関数は、その文字列から元のオブジェクトを復元(デシリアライズ)します。Dom\ProcessingInstructionオブジェクトは、XML文書内の処理命令を表しますが、これは単独で存在するものではなく、必ず文書全体を表すDom\Documentオブジェクトの一部として機能しなければなりません。デシリアライズの過程で、この親子関係が失われ、オブジェクトが不完全な状態になってしまう可能性があります。__wakeupメソッドは、unserializeが実行された直後に自動的に呼び出され、復元されたオブジェクトが適切なDom\Documentに属しているかを確認します。もしオブジェクトがどのドキュメントにも関連付けられていない不正な状態であった場合、このメソッドは例外をスローして処理を中断させます。これにより、不完全なオブジェクトが使用されることで発生する予期せぬエラーを防ぎ、プログラムの安定性を確保する重要な役割を担っています。
構文(syntax)
1<?php 2 3$doc = new DOMDocument(); 4$pi = $doc->createProcessingInstruction('php', 'echo "Hello";'); 5 6$serialized = serialize($pi); 7 8try { 9 // Dom\ProcessingInstruction::__wakeup() はアンシリアライズを防ぐため、 10 // unserialize() を試みると例外がスローされます。 11 unserialize($serialized); 12} catch (Dom\Exception $e) { 13 echo $e->getMessage(); 14}
引数(parameters)
引数なし
引数はありません
戻り値(return)
void
このメソッドは、オブジェクトをシリアライズ解除する際に内部的に呼び出されます。戻り値はありません。
サンプルコード
PHP8での__wakeup()バイパス対策
1<?php 2 3/** 4 * __wakeup() メソッドの挙動を説明するためのクラス 5 * 6 * このクラスは、デシリアライズ時に __wakeup() メソッドが 7 * 自動的に呼び出されることを示します。 8 */ 9class ExampleObject 10{ 11 /** 12 * @var string データ 13 */ 14 public string $data; 15 16 /** 17 * @var bool オブジェクトが正しく初期化されたかを示すフラグ 18 */ 19 private bool $initialized = false; 20 21 public function __construct(string $data) 22 { 23 $this->data = $data; 24 $this->initialized = true; 25 echo "オブジェクトが生成されました。 (data: {$this->data})" . PHP_EOL; 26 } 27 28 /** 29 * unserialize() 時に自動的に呼び出されるマジックメソッド 30 * 31 * オブジェクトのデシリアライズ後に、状態を再初期化するなどの 32 * 処理をここで行います。 33 */ 34 public function __wakeup(): void 35 { 36 $this->initialized = true; 37 echo "__wakeup() が呼び出されました。オブジェクトの状態を再初期化します。" . PHP_EOL; 38 } 39 40 public function showStatus(): void 41 { 42 if ($this->initialized) { 43 echo "状態: 初期化済み (data: {$this->data})" . PHP_EOL; 44 } else { 45 echo "状態: 未初期化です!" . PHP_EOL; 46 } 47 } 48} 49 50// 1. オブジェクトを生成し、シリアライズする 51echo "--- オブジェクトの生成とシリアライズ ---" . PHP_EOL; 52$obj = new ExampleObject('secret data'); 53$serializedObj = serialize($obj); 54echo "シリアライズされたデータ: " . $serializedObj . PHP_EOL; 55echo PHP_EOL; 56 57 58// 2. __wakeup() バイパス攻撃の試み (PHP 7.1以降では対策済み) 59// 古いPHPバージョンでは、シリアライズされた文字列内のプロパティ数を 60// 実際の数より多く書き換えることで __wakeup() の呼び出しを回避できました。 61// 例: O:13:"ExampleObject":2 -> O:13:"ExampleObject":3 62// しかし、PHP 8ではこの脆弱性は修正されており、デシリアライズ自体が失敗します。 63echo "--- __wakeup() バイパス攻撃の試み ---" . PHP_EOL; 64 65// プロパティ数を意図的に不正な値 (1 -> 10) に書き換える 66$maliciousSerializedObj = str_replace( 67 'O:13:"ExampleObject":2:', 68 'O:13:"ExampleObject":10:', 69 $serializedObj 70); 71echo "改ざんされたデータ: " . $maliciousSerializedObj . PHP_EOL; 72 73// error_reporting を設定して、NOTICE を補足できるようにする 74error_reporting(E_ALL); 75 76// 改ざんされたデータをデシリアライズしようと試みる 77// PHP 8では、プロパティ数が一致しないため、__wakeup() は呼び出されず、 78// unserialize() は false を返し、NOTICE が発生します。 79$unserializedObj = unserialize($maliciousSerializedObj); 80 81var_dump($unserializedObj); // bool(false) が出力される 82 83if ($unserializedObj instanceof ExampleObject) { 84 // このブロックは実行されない 85 echo "デシリアライズに成功しました。" . PHP_EOL; 86 $unserializedObj->showStatus(); 87} else { 88 echo "デシリアライズに失敗しました。PHP 8では__wakeup()バイパスは防止されています。" . PHP_EOL; 89}
__wakeupメソッドは、unserialize()関数によってシリアライズされたオブジェクトが復元される際に、自動的に呼び出される特殊なメソッドです。このメソッドに引数はなく、戻り値もありません(void)。デシリアライズ後に、データベース接続の再確立やプロパティの再初期化など、オブジェクトを再び利用可能な状態に戻すための処理を記述するのが一般的な使い方です。
このサンプルコードは、__wakeupメソッドの挙動と、関連する過去の脆弱性について説明しています。コードの前半では、オブジェクトがシリアライズされ、その後unserialize()で復元される際に__wakeupメソッドが自動で実行される様子を示しています。
コードの後半では、「__wakeup bypass」という攻撃手法を試みています。これは、シリアライズされた文字列データ内のプロパティ数を不正な値に書き換えることで、__wakeupメソッドの実行を意図的にスキップさせる脆弱性でした。しかし、サンプルコードが示すように、PHP 8ではこの脆弱性は対策済みです。改ざんされたデータをデシリアライズしようとすると、処理は失敗してfalseを返し、__wakeupメソッドが呼び出されることはありません。これにより、オブジェクトが不完全な状態で利用される危険性を防いでいます。
__wakeupは、unserialize()関数でオブジェクトが復元される際に自動的に呼び出される特殊なメソッドです。データベース接続の再確立など、復元後の初期化処理に使われます。サンプルコードにある、シリアライズされたデータを改ざんして__wakeupの実行を回避する手法は、古いPHPの脆弱性を利用したものです。現在のPHP 8ではこの脆弱性は修正されており、不正なデータを渡すとデシリアライズ自体が失敗するため安全性が向上しています。しかし、unserialize()は信頼できない文字列を扱うと、依然としてセキュリティ上の危険を伴う可能性があります。そのため、外部から受け取ったデータを扱う場合は、より安全なjson_encode()やjson_decode()の使用を検討することが推奨されます。
PHP __wakeup メソッドでオブジェクトを復元する
1<?php 2 3/** 4 * __wakeup マジックメソッドの動作を実演するためのクラス 5 * 6 * このクラスは、オブジェクトがデシリアライズ(unserialize)される際に 7 * 自動的に呼び出される __wakeup メソッドの挙動を示します。 8 * データベース接続のような、シリアライズできないリソースを 9 * オブジェクト復元時に再初期化するなどの用途で利用されます。 10 */ 11class ConnectionManager 12{ 13 /** @var string 接続設定情報 */ 14 private string $dsn; 15 16 /** @var ?string 接続状態を表すプロパティ(リソースの代用) */ 17 private ?string $connectionStatus = null; 18 19 public function __construct(string $dsn) 20 { 21 $this->dsn = $dsn; 22 $this->connect(); 23 } 24 25 /** 26 * 接続処理を模倣するメソッド 27 */ 28 private function connect(): void 29 { 30 // 実際にはここでデータベース接続などを行う 31 $this->connectionStatus = "CONNECTED"; 32 echo "データベースに接続しました。({$this->dsn})" . PHP_EOL; 33 } 34 35 /** 36 * serialize() 実行時に呼び出されるマジックメソッド 37 * 38 * シリアライズするプロパティの配列を返します。 39 * ここでは接続状態($connectionStatus)は含めず、設定情報($dsn)のみを保存します。 40 */ 41 public function __sleep(): array 42 { 43 echo "__sleep: シリアライズします。接続状態は保存されません。" . PHP_EOL; 44 $this->connectionStatus = null; // 接続を切断 45 return ['dsn']; 46 } 47 48 /** 49 * unserialize() 実行時に呼び出されるマジックメソッド 50 * 51 * オブジェクトの復元が完了した後に、必要な初期化処理を再実行します。 52 * ここでは、失われたデータベース接続を再確立しています。 53 */ 54 public function __wakeup(): void 55 { 56 echo "__wakeup: デシリアライズしました。データベースに再接続します。" . PHP_EOL; 57 $this->connect(); 58 } 59 60 /** 61 * 現在の接続状態を表示します。 62 */ 63 public function printStatus(): void 64 { 65 echo "現在の接続状態: " . ($this->connectionStatus ?? 'DISCONNECTED') . PHP_EOL; 66 } 67} 68 69// 1. オブジェクトを生成し、初期状態を確認 70echo "--- 1. オブジェクト生成 ---" . PHP_EOL; 71$manager = new ConnectionManager("mysql:host=localhost;dbname=test"); 72$manager->printStatus(); 73echo PHP_EOL; 74 75// 2. オブジェクトをシリアライズ(文字列に変換) 76// この処理で __sleep() が呼び出されます。 77echo "--- 2. シリアライズ実行 ---" . PHP_EOL; 78$serializedManager = serialize($manager); 79echo "シリアライズ後のデータ: " . $serializedManager . PHP_EOL; 80echo PHP_EOL; 81 82// 3. シリアライズされた文字列からオブジェクトを復元 83// この処理で __wakeup() が自動的に呼び出されます。 84echo "--- 3. デシリアライズ実行 ---" . PHP_EOL; 85/** @var ConnectionManager $restoredManager */ 86$restoredManager = unserialize($serializedManager); 87$restoredManager->printStatus(); 88 89?>
__wakeupは、PHPの「マジックメソッド」の一つで、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に自動的に呼び出されます。このメソッドは引数を取らず、何も値を返す必要がありません(戻り値は void)。主な目的は、オブジェクトが復元された後、再度利用可能にするための初期化処理を行うことです。
サンプルコードでは、データベース接続を管理するクラスを例にしています。まず、serialize()関数でオブジェクトを文字列に変換する際、__sleepメソッドが呼ばれ、データベース接続のような一時的な情報は保存されません。
その後、unserialize()関数で文字列からオブジェクトを復元すると、__wakeupメソッドが自動的に実行されます。このメソッドの内部で、失われていたデータベースへの再接続処理を呼び出しています。これにより、復元されたオブジェクトは、再びデータベースと通信できる状態に戻ります。
このように__wakeupメソッドは、ファイル保存やセッション管理などでオブジェクトを復元する際に、データベース接続の再確立や、一時ファイルの再オープンなど、必要な「目覚め」の処理を定義するために利用されます。
__wakeupは、unserialize()関数でオブジェクトを復元した直後に自動で呼び出される特殊なメソッドです。自分で直接呼び出すものではありません。主に、シリアライズ(オブジェクトの保存)時に__sleepメソッドなどで破棄したデータベース接続やファイルハンドルなどのリソースを再確立する目的で利用します。引数はなく、何も返す必要はありません。注意点として、信頼できない文字列をunserialize()すると、__wakeupが悪用されセキュリティ上の問題を引き起こす可能性があります。そのため、デシリアライズするデータは信頼できるものに限定することが重要です。