Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【PHP8.x】DOMNotation::__wakeup()メソッドの使い方

__wakeupメソッドの使い方について、初心者にもわかりやすく解説します。

作成日: 更新日:

基本的な使い方

『__wakeupメソッドは、シリアライズされたDOMNotationオブジェクトが復元される際に、その処理を意図的に失敗させるために例外を発生させるメソッドです。PHPには、unserialize()関数によってオブジェクトがデータから復元される際に自動的に呼び出される__wakeupという特別なメソッドがあります。通常、このメソッドはデータベース接続の再確立など、オブジェクトが復元された後の初期化処理に使われます。しかし、DOMNotationオブジェクトは、それが属するDOMDocumentオブジェクトと密接に結びついており、単独でシリアライズ(保存可能な形式に変換すること)して後から復元することは想定されていません。もし復元できてしまうと、元のドキュメントとの関連性が失われ、オブジェクトが不正な状態になる危険性があります。このため、DOMNotationクラスの__wakeupメソッドは、復元処理が試みられた際に意図的にエラーを発生させることで、不正な状態のオブジェクトが生成されるのを防ぎます。したがって、このメソッドは開発者が直接呼び出すものではなく、PHPのシリアライズ機構においてオブジェクトの整合性を保証するために内部的に使用されるものです。

構文(syntax)

1public function __wakeup(): void

引数(parameters)

引数なし

引数はありません

戻り値(return)

void

このメソッドは、オブジェクトがデシリアライズ(保存された状態から復元)された際に自動的に呼び出されます。オブジェクトの初期化や関連リソースの再設定など、デシリアライズ後の処理を行います。戻り値はありません。

サンプルコード

PHP __wakeup バイパスの歴史と現状

1<?php
2
3declare(strict_types=1);
4
5/**
6 * __wakeup() の挙動と、古いPHPに存在したバイパス脆弱性の概念を示すクラス
7 *
8 * このクラスは、デシリアライズ時に特定の処理を行う __wakeup() メソッドを持ちます。
9 * かつて、シリアライズされた文字列を改ざんすることで、この __wakeup() の
10 * 呼び出しを意図的にスキップ(バイパス)できる脆弱性 (CVE-2016-7124) が
11 * ありましたが、現在のPHPバージョンでは対策されています。
12 */
13class UserSession
14{
15    public string $username;
16    public bool $isAdmin = false;
17
18    /**
19     * オブジェクトが unserialize() によって復元される際に自動的に呼び出されるマジックメソッド
20     *
21     * 本来は、データベース接続の再確立など、リソースの再初期化処理をここで行います。
22     * このデモでは、メソッドが呼び出されたことを示すメッセージを出力します。
23     */
24    public function __wakeup(): void
25    {
26        echo "__wakeup() が呼び出されました。セッション情報を検証・初期化します。" . PHP_EOL;
27        // 例: 実際のアプリケーションでは、ここでユーザー権限を再検証する
28        $this->isAdmin = false;
29        echo "ユーザー '{$this->username}' の管理者権限はリセットされました。" . PHP_EOL;
30    }
31}
32
33// 1. 通常のオブジェクトを生成し、シリアライズする
34echo "--- 正常な処理 ---" . PHP_EOL;
35$session = new UserSession();
36$session->username = 'test_user';
37$session->isAdmin = true; // 管理者として設定
38$serializedData = serialize($session);
39echo "元のシリアライズ文字列: " . $serializedData . PHP_EOL;
40
41// 2. 通常通りデシリアライズする
42// __wakeup() が呼び出され、isAdmin プロパティが false にリセットされる
43$restoredSession = unserialize($serializedData);
44echo PHP_EOL;
45
46
47// 3. __wakeup() のバイパスを試みる (現在は対策済みで失敗する)
48echo "--- バイパス試行 (現在のPHPでは失敗) ---" . PHP_EOL;
49
50// 脆弱性を悪用するため、シリアライズ文字列内のプロパティ数を不正に書き換える
51// "O:11:\"UserSession\":2:" (プロパティ数: 2) -> "O:11:\"UserSession\":3:" (プロパティ数: 3)
52// 古いPHPでは、この改ざんにより __wakeup() の呼び出しがスキップされた。
53$maliciousData = str_replace('O:11:"UserSession":2:', 'O:11:"UserSession":3:', $serializedData);
54echo "改ざんされたシリアライズ文字列: " . $maliciousData . PHP_EOL;
55
56// 4. 改ざんされた文字列をデシリアライズしようと試みる
57// 現在のPHP (7.1以降) では、プロパティ数が不正であるためデシリアライズ自体が失敗し、
58// オブジェクトは生成されず、__wakeup() も呼び出されない。
59// unserialize() は false を返し、E_NOTICE が発生する。
60$result = unserialize($maliciousData);
61
62echo "デシリアライズ結果: " . PHP_EOL;
63var_dump($result);
64
65if ($result === false) {
66    echo "デシリアライズに失敗しました。__wakeup() のバイパスは阻止されました。" . PHP_EOL;
67}

__wakeup()は、unserialize()関数によってシリアライズされた文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。このメソッドは引数を持たず、値を返しません(void)。データベース接続の再確立など、オブジェクト復元時の初期化処理を記述するために使用されます。

このサンプルコードは、まずisAdminプロパティがtrueUserSessionオブジェクトをserialize()で文字列化します。次に、これをunserialize()で復元すると、__wakeup()が自動で実行され、セキュリティ上の配慮からisAdminプロパティがfalseにリセットされることを示しています。

さらに、コードの後半では過去に存在した脆弱性の概念を説明しています。古いPHPでは、シリアライズ文字列内のプロパティ数を不正に書き換えることで__wakeup()の実行を回避(バイパス)し、意図しない状態でオブジェクトを復元させることが可能でした。しかし、現在のPHPではこの問題は修正済みです。そのため、改ざんされた文字列でunserialize()を試みると、デシリアライズ自体が失敗してfalseが返され、バイパスが阻止されることを確認できます。

__wakeup()は、unserialize()関数でオブジェクトが復元される際に自動で呼び出される特殊なメソッドです。主にデータベース接続の再確立など、デシリアライズ時に必要な初期化処理を安全に行うために使用されます。サンプルコードで示されている、シリアライズ文字列の改ざんによって__wakeup()の呼び出しを回避する脆弱性は、古いPHPバージョンに存在したもので、現在のPHPでは対策済みです。そのため、不正なデータを渡すとデシリアライズは失敗し、unserialize()falseを返します。最も重要な注意点として、信頼できない外部からのデータをunserialize()することは、この脆弱性の有無にかかわらず、意図しないコード実行などの深刻なセキュリティリスクに繋がるため、原則として避けるべきです。

PHP __wakeup で接続を再確立する

1<?php
2
3/**
4 * __wakeupマジックメソッドの使用例を示すクラス
5 *
6 * このクラスは、オブジェクトが非直列化(unserialize)される際に、
7 * 特定の処理(リソースの再接続など)を自動的に実行する方法を示します。
8 */
9class ConnectionManager
10{
11    /** @var string 接続情報 */
12    private string $dsn;
13
14    /** @var ?string 接続リソース(シリアライズされない) */
15    private ?string $connection = null;
16
17    /**
18     * コンストラクタ
19     *
20     * @param string $dsn データソース名
21     */
22    public function __construct(string $dsn)
23    {
24        $this->dsn = $dsn;
25        $this->connect();
26        echo "オブジェクトが生成され、接続が確立されました。" . PHP_EOL;
27    }
28
29    /**
30     * 接続を確立する(この例では文字列をセットするだけ)
31     */
32    private function connect(): void
33    {
34        $this->connection = "CONNECTED_TO_" . $this->dsn;
35    }
36
37    /**
38     * シリアライズ時に呼び出されるマジックメソッド
39     *
40     * シリアライズするプロパティの配列を返します。
41     * ここでは$connectionを除外しているため、接続リソースは保存されません。
42     *
43     * @return string[]
44     */
45    public function __sleep(): array
46    {
47        echo "__sleep()が呼び出されました。接続を破棄します。" . PHP_EOL;
48        $this->connection = null; // 接続を明示的に閉じる
49        return ['dsn']; // $dsnプロパティのみをシリアライズ対象とする
50    }
51
52    /**
53     * unserialize() 実行時に呼び出されるマジックメソッド
54     *
55     * オブジェクトが復元された後に、必要な初期化処理(例: データベース接続の再確立)を行います。
56     * DOMNotation::__wakeup も同様の目的で内部的に使用されます。
57     */
58    public function __wakeup(): void
59    {
60        echo "__wakeup()が呼び出されました。接続を再確立します..." . PHP_EOL;
61        $this->connect(); // 接続を再確立する
62    }
63
64    /**
65     * 現在の状態を表示する
66     */
67    public function displayStatus(): void
68    {
69        var_dump($this);
70    }
71}
72
73// 1. オブジェクトをインスタンス化
74echo "--- 1. オブジェクト生成 ---" . PHP_EOL;
75$manager = new ConnectionManager('mysql:dbname=sample_db');
76$manager->displayStatus();
77echo PHP_EOL;
78
79// 2. オブジェクトをシリアライズ(文字列に変換)
80// この時、__sleep()が呼び出されます。
81echo "--- 2. シリアライズ実行 ---" . PHP_EOL;
82$serializedManager = serialize($manager);
83echo "シリアライズされたデータ: " . $serializedManager . PHP_EOL;
84echo PHP_EOL;
85
86// 3. 文字列からオブジェクトを復元(アンシリアライズ)
87// この時、__wakeup()が呼び出され、接続が再確立されます。
88echo "--- 3. アンシリアライズ実行 ---" . PHP_EOL;
89$restoredManager = unserialize($serializedManager);
90
91// 復元されたオブジェクトの状態を確認
92echo "--- 4. 復元後のオブジェクトの状態 ---" . PHP_EOL;
93$restoredManager->displayStatus();
94
95?>

__wakeup は、PHPのマジックメソッドの一つで、unserialize() 関数によって文字列からオブジェクトが復元(非直列化)される際に、自動的に呼び出されます。このメソッドの主な目的は、オブジェクトが復元された後に必要な初期化処理を行うことです。例えば、データベースへの接続やファイルハンドルなど、シリアライズの過程で失われてしまうリソースを再確立する処理を記述します。__wakeup メソッドは引数を持たず、戻り値もありません(void)。

サンプルコードの ConnectionManager クラスでは、serialize() 実行時に __sleep メソッドが呼ばれ、データベース接続を表すプロパティが意図的に破棄されます。その後、unserialize() でオブジェクトを復元する際に __wakeup メソッドが自動的に呼び出されます。このメソッド内で接続処理を再度実行することにより、オブジェクトは利用可能な状態へと復元されます。このように、__wakeup はオブジェクトを保存・復元する際に、その整合性や機能を維持するために重要な役割を果たします。リファレンス情報にある DOMNotation::__wakeup も、内部的に同様の目的で、DOMオブジェクトが復元される際の初期化処理に使用されています。

__wakeupメソッドは、unserialize()関数でオブジェクトが復元される際に自動で呼び出される特別なメソッドです。主な用途は、データベース接続の再確立など、シリアライズ(保存)できないリソースを再初期化することです。serialize()時に呼ばれる__sleepメソッドと対になっており、__sleepでリソースを解放し、__wakeupで再接続するという使い方が一般的です。最も重要な注意点は、信頼できない外部の文字列をunserialize()しないことです。意図せず__wakeup内のコードが実行され、セキュリティ上の脆弱性につながる危険があるため、渡すデータは慎重に扱う必要があります。また、このメソッドはオブジェクトの復元時にのみ動作し、新規作成時のコンストラクタとは役割が異なります。

関連コンテンツ

関連プログラミング言語