FlawOpen

flawopen.com/安全でないデシリアライズ/PHP unserialize()

信頼できない入力に PHP unserialize() は安全ですか?

CWE-502: Deserialization of Untrusted Dataリファレンスページ
要点

allowed_classes を指定しても、信頼できない入力を unserialize() に渡してはいけません。PHP はオブジェクトを復元し、__unserialize() や __wakeup() を呼び出す場合があります。攻撃者はアプリやオートローダーで利用可能なクラスを悪用する可能性があります。信頼できないデータには JSON を使います。連想配列としてデコードすれば、JSON オブジェクトは配列になり、魔法メソッドを持つアプリケーションクラスは生成されません。

脆弱
<?php
// Cookie content is controlled by the client.
$input = $_COOKIE['prefs'] ?? '';
$data = unserialize($input);

// Restoring an object may invoke its
// __unserialize() or __wakeup() method.
// Later object destruction may invoke __destruct().

// The risk depends on available classes,
// their serialized state, and reachable behavior.
修正済み
<?php
$input = $_POST['prefs'] ?? null;
if (!is_string($input)) {
    http_response_code(400);
    exit;
}

try {
    $data = json_decode(
        $input,
        true,
        512,
        JSON_THROW_ON_ERROR
    );
} catch (JsonException $e) {
    http_response_code(400);
    exit;
}

if (!is_array($data)) {
    http_response_code(400);
    exit;
}

// Validate required keys and value types here.

PHP のオブジェクトインジェクションチェーンの成立条件

オブジェクトインジェクションが自動的なコード実行を意味するわけではありません。シリアライズ値は PHP が解決でき、オプションで許可されたクラスを指定し、復元または後続処理でセキュリティに影響する動作へ到達する必要があります。復元時に PHP は __unserialize() や __wakeup() を呼び出すことがあります。破棄時に __destruct()、文字列コンテキストでは __toString() が実行される場合があります。

Property-Oriented Programming (POP) チェーンは、アプリやロード済み依存関係に既にある動作をつなぎます。悪用可能性は実際のクラス、シリアライズされたプロパティ、呼び出し経路、実行条件によって決まります。対象の gadget チェーンを実証する必要があり、unserialize() の存在だけでは不十分です。

文字列フィルターや allowed_classes だけでは完全に防げない理由

O: などのマーカーを拒否したり、クラス名や文字列長を制限したりしても、PHP シリアライズ形式を安全に検証できません。PHP は allowed_classes の値にかかわらず、信頼できない入力を unserialize() に渡さないよう明示的に警告しています。許可されないクラスは __PHP_Incomplete_Class になりますが、構造自体は解析されます。信頼できる既存データを扱う場合は結果の形を検証し、深さを制限してください。クライアント状態には JSON を使います。

よくある質問

allowed_classes => false なら信頼できない入力も安全ですか?

いいえ。通常のアプリクラスの復元を防ぎ、許可されないオブジェクトは __PHP_Incomplete_Class になります。それでも PHP は、この設定にかかわらず信頼できない入力を unserialize() に渡さないよう警告しています。信頼できる既存データへの多層防御としてのみ使い、結果を検証し max_depth を制限してください。 PHP マニュアルによると、このオプションは列挙型には適用されません。

HMAC でシリアライズデータを安全にできますか?

鍵を秘密に保ち、アプリが生成したデータの署名をデシリアライズ前に hash_equals() で検証すれば、完全性を確認できます。解析前に署名が無効なデータを拒否してください。HMAC は暗号化せず、リプレイも防ぎません。必要なら有効期限や nonce を追加します。任意のユーザー入力を信頼できるものにするため署名してはいけません。

Python pickle や Java ObjectInputStream と同じリスクですか?

いずれもシリアライズデータからオブジェクトを復元するリスクがありますが、形式、フック、クラス解決、対策は異なります。各ランタイムと利用可能クラスを個別に評価し、ある環境の gadget チェーンが他でも使えると考えないでください。

参考資料

関連項目 安全でないデシリアライズ:リスクと対策 | flawopen.com Python pickle.load():信頼できないデータのリスク Ruby YAML.load は安全?Psych のバージョンと safe_load .NET BinaryFormatter:リスクと移行 Java ObjectInputStreamの安全性とデシリアライズ対策 Newtonsoft.Json TypeNameHandling:安全な設定方法