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

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

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

作成日: 更新日:

基本的な使い方

__wakeupメソッドは、unserialize関数によってオブジェクトが復元される際にコールバックされるマジックメソッドです。Dom\Notationクラスにおいて、このメソッドはオブジェクトの整合性を維持するために重要な役割を果たします。

PHPのunserialize関数は、シリアライズされた文字列からオブジェクトを再構築しますが、その過程でオブジェクトの状態が不正になる可能性があります。特に、外部リソースへの参照や、オブジェクト間の関連性が重要な場合、単純な復元だけでは元の状態を正確に再現できないことがあります。

Dom\Notationクラスの__wakeupメソッドは、オブジェクトがunserializeされる際に自動的に実行され、オブジェクトの状態を初期化したり、必要なリソースを再確保したりする処理を記述するために使用されます。例えば、データベース接続を確立し直したり、キャッシュをリフレッシュしたり、他のオブジェクトとの関係性を再構築したりする処理などが考えられます。

Dom\NotationクラスはDOM(Document Object Model)を扱うためのクラスであり、XML文書の構造を表現します。したがって、__wakeupメソッドは、DOM構造の整合性を維持するために利用されることが想定されます。たとえば、unserializeされたオブジェクトが参照する他のDOMノードが存在するか確認したり、必要なノードを再構築したりする処理を行うことができます。

このメソッドを適切に実装することで、unserializeされたオブジェクトが、シリアライズされる前の状態とできる限り近い状態になるように制御できます。これにより、アプリケーションの信頼性と安定性を向上させることが可能になります。__wakeupメソッドは引数を取りません。

構文(syntax)

1public Dom\Notation::__wakeup(): void

引数(parameters)

引数なし

引数はありません

戻り値(return)

void

__wakeup メソッドは、オブジェクトが unserialize() されて再構築された際に自動的に呼び出されます。このメソッドは、オブジェクトの初期化や状態の復元を行うために使用されます。戻り値はありません。

サンプルコード

PHP __wakeup バイパス脆弱性デモ

1<?php
2
3declare(strict_types=1);
4
5/**
6 * __wakeup() の呼び出しを回避する脆弱性 (CVE-2016-7124) の概念を説明するクラス。
7 *
8 * この脆弱性は古いPHPバージョンに存在しましたが、PHP 7.4以降では修正されています。
9 * このコードは、現在のPHPでは脆弱性が機能しないことを示します。
10 */
11class Demonstration
12{
13    /** @var string|null データプロパティ */
14    public ?string $data = 'Initialized';
15
16    /**
17     * オブジェクトが unserialize されるときに自動的に呼び出されるマジックメソッド。
18     * 本来は、ここでリソースの再接続や状態の初期化を行い、安全性を確保します。
19     * このメソッドの呼び出しを回避されると、意図しない状態でオブジェクトが復元される危険性がありました。
20     */
21    public function __wakeup(): void
22    {
23        echo "__wakeup() was called. The object state has been reset.\n";
24        // 意図しないデータを無効化するなどのサニタイズ処理を想定
25        $this->data = 'Safely reset';
26    }
27
28    /**
29     * デストラクタ: オブジェクトが破棄されるときに呼び出される。
30     * 脆弱性が悪用されると、改ざんされたデータを持つオブジェクトのデストラクタが
31     * 意図せず実行され、問題を引き起こす可能性がありました。
32     */
33    public function __destruct()
34    {
35        echo "Destructor called. Data is: '{$this->data}'\n";
36    }
37}
38
39// 脆弱性を悪用しようとするペイロードを作成します。
40// 元のオブジェクト: new Demonstration() はプロパティが1つです。
41// "O:13:\"Demonstration\":1:{s:4:\"data\";s:18:\"Malicious Payload!\";}"
42// ペイロードでは、プロパティ数を実際の数より多く宣言します (例: 1 -> 2)。
43// これにより、古いPHPバージョンでは __wakeup() の呼び出しがスキップされました。
44$payload = 'O:13:"Demonstration":2:{s:4:"data";s:18:"Malicious Payload!";}';
45
46echo "Attempting to unserialize a malicious payload...\n";
47echo "Payload: {$payload}\n\n";
48
49// 現在のPHPバージョンでペイロードを unserialize します。
50// PHP 7.4以降では、プロパティ数が一致しないため unserialize() は失敗し、
51// __wakeup() は呼び出されず、オブジェクトも生成されません。
52// これにより、脆弱性が修正されていることが確認できます。
53$object = unserialize($payload);
54
55echo "\n--- Result ---\n";
56if ($object === false) {
57    echo "Unserialization failed. The vulnerability does not exist.\n";
58    echo "__wakeup() was NOT bypassed.\n";
59} else {
60    // このブロックは、脆弱性が存在する古いPHPバージョンでのみ実行されます。
61    echo "Unserialization successful. __wakeup() was bypassed.\n";
62    var_dump($object);
63}
64
65// スクリプト終了時にデストラクタが呼ばれることを示すための区切り線
66echo "\nEnd of script.\n";
67

__wakeup()は、unserialize()関数によって文字列からオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。このメソッドは引数を取らず、戻り値もありません(void)。主な役割は、オブジェクトが復元された直後に、データベース接続の再確立やプロパティの初期化といった、安全な状態に戻すための準備処理を行うことです。

このサンプルコードは、過去のPHPバージョンに存在した__wakeup()メソッドの呼び出しを意図的に回避できてしまう脆弱性の概念を解説しています。脆弱性があったバージョンでは、unserialize()に渡すデータ内のプロパティ数を実際の数より多く設定した不正な文字列を渡すことで、__wakeup()の実行をスキップさせることが可能でした。これにより、メソッド内で行われるはずだったセキュリティチェックや初期化処理が実行されず、不正なデータを持った危険なオブジェクトが生成されてしまう問題がありました。

しかし、この脆弱性はPHP 7.4以降で修正されています。現在のPHPでこのコードを実行すると、unserialize()関数がプロパティ数の不一致を検知して処理に失敗し、falseを返します。その結果、不正なオブジェクトは生成されず、__wakeup()がバイパスされることもありません。このコードは、現在のPHP環境ではこの脆弱性が存在しないことを示しています。

このサンプルコードは、PHPのunserialize()関数が持つ過去の脆弱性について示しています。注意点は、ユーザーが入力したデータなど、信頼できない文字列をunserialize()で安易にオブジェクトへ復元しないことです。これは、意図しないプログラムの実行につながる深刻なセキュリティリスクを生む可能性があります。__wakeupメソッドは、unserialize()時にオブジェクトの状態を安全に初期化するために自動で呼び出される仕組みです。このコードで示されている攻撃は、古いPHPバージョンにおいてプロパティ数を偽装することで__wakeupの実行を回避するものでしたが、PHP 7.4以降では修正されています。現在のバージョンでは不正なデータで復元が失敗するため安全ですが、常に最新のPHPを利用し、unserialize()の取り扱いには細心の注意を払うことが重要です。

PHP __wakeupで接続を復元する

1<?php
2
3declare(strict_types=1);
4
5/**
6 * __wakeup マジックメソッドの動作を示すサンプルクラス
7 *
8 * データベース接続など、シリアライズ(文字列化)できないリソースを持つ
9 * オブジェクトが、デシリアライズ(復元)される際の処理を模倣します。
10 */
11class Connection
12{
13    private string $dsn;
14
15    // データベース接続リソースを模倣したプロパティ
16    private ?stdClass $resource = null;
17
18    public function __construct(string $dsn)
19    {
20        $this->dsn = $dsn;
21        $this->connect();
22    }
23
24    // 接続処理をシミュレートします。
25    private function connect(): void
26    {
27        echo "データベースに接続しました。({$this->dsn})" . PHP_EOL;
28        $this->resource = new stdClass();
29    }
30
31    public function isConnected(): bool
32    {
33        return $this->resource !== null;
34    }
35
36    /**
37     * serialize()実行時に呼ばれます。
38     * シリアライズ対象のプロパティ名を配列で返します。
39     * ここで接続リソース(resource)を除外します。
40     */
41    public function __sleep(): array
42    {
43        echo "-> __sleep() が呼ばれました。シリアライズの準備をします。" . PHP_EOL;
44        $this->resource = null; // リソースを解放
45        return ['dsn'];
46    }
47
48    /**
49     * unserialize()実行時に呼ばれ、オブジェクトの状態を再構築します。
50     * このメソッドで、失われたデータベース接続などを再確立します。
51     * Dom\Notation::__wakeup も同様に、内部リソースを再初期化するために存在します。
52     */
53    public function __wakeup(): void
54    {
55        echo "-> __wakeup() が呼ばれました。接続を再確立します。" . PHP_EOL;
56        $this->connect();
57    }
58}
59
60// 1. オブジェクトを生成します。コンストラクタで接続が確立されます。
61$db = new Connection('mysql:host=localhost');
62echo '生成後、接続されていますか? ' . ($db->isConnected() ? 'はい' : 'いいえ') . PHP_EOL;
63echo PHP_EOL;
64
65// 2. オブジェクトをシリアライズ(文字列に変換)します。
66//    このとき、__sleep()が呼ばれ、接続リソースが破棄されます。
67$serializedDb = serialize($db);
68echo "シリアライズ後のデータ: " . $serializedDb . PHP_EOL;
69echo 'シリアライズ後、接続されていますか? ' . ($db->isConnected() ? 'はい' : 'いいえ') . PHP_EOL;
70echo PHP_EOL;
71
72// 3. 文字列からオブジェクトをデシリアライズ(復元)します。
73//    このとき、__wakeup()が呼ばれ、接続が自動的に再確立されます。
74$restoredDb = unserialize($serializedDb);
75echo '復元後、接続されていますか? ' . ($restoredDb->isConnected() ? 'はい' : 'いいえ') . PHP_EOL;
76
77?>

__wakeup()は、unserialize()関数によってシリアライズ(文字列化)されたオブジェクトが復元される際に、自動的に呼び出されるマジックメソッドです。このメソッドは引数を取らず、戻り値もありません(void)。主な役割は、シリアライズの過程で失われた可能性のあるリソース(データベース接続など)を再初期化し、オブジェクトが再び正常に利用できる状態を整えることです。

サンプルコードでは、データベース接続を模倣したConnectionクラスを用いてこの動作を説明しています。まず、serialize()関数でオブジェクトを文字列に変換する際、関連するマジックメソッド__sleep()が呼ばれ、接続リソースが意図的に破棄されます。その後、unserialize()関数で文字列からオブジェクトを復元する際に__wakeup()が自動的に実行されます。このメソッドの内部で再度データベースへの接続処理を行うことで、オブジェクトは完全に機能する状態へと復元されます。

Dom\Notationクラスに__wakeup()メソッドが存在するのも、これと同様の理由です。XMLドキュメントを扱うための内部的な状態やリソースを、デシリアライズ(復元)時に再構築するために利用されます。

__wakeupは、unserialize()関数によって文字列からオブジェクトが復元される際に自動的に呼び出される特別なメソッドです。オブジェクトの新規作成時に呼ばれる__constructとは異なり、復元時の初期化処理を担当します。サンプルコードのように、データベース接続などシリアライズ(文字列化)の過程で失われるリソースを再接続・再初期化する目的で利用されます。多くの場合、シリアライズ時に呼ばれる__sleepと対で使われます。重要な注意点として、ユーザー入力など信頼性の低いデータをunserialize()に渡すと、深刻なセキュリティ脆弱性につながる可能性があるため、使用には十分な注意が必要です。

関連コンテンツ

関連プログラミング言語