👻

msgpack-phpはオブジェクトもシリアライズする

2022/01/17に公開

PHPで安全でないデシリアライゼーションを学ぼうを読み、ふと思い出したので書いてみる。

msgpack-phpでオブジェクトをシリアライズする

msgpackのPHP extension msgpack-phpは、PHPのオブジェクトもシリアライズ可能である。

$object = new stdClass();
$object->count=1;
$object->label='sample';
file_put_contents('serialized.data', msgpack_pack($object));

作成したファイルを読んでみよう

$serializedData = file_get_contents('serialized.data');
$data=msgpack_unpack($serializedData);
echo $data->count;
$ php msgpack-unpack.php
1

もちろん、これはmsgpackの仕様に含まれない動作だ。
それについては後でおまけとして触れる。

デストラクタのあるオブジェクトをシリアライズする

class Foo{
    public function __destruct()
    {
        echo "実行";
    }
};
$class = new Foo();
file_put_contents('msgpack.data', msgpack_pack($class));

ここで出力されるファイルの中身を見てみよう。

00000000: 81c0 a346 6f6f                           ...Foo

たったこれだけなのだが……

デシリアライズする

class Foo {
    public function __destruct()
    {
        echo "実行";
    }
};
$serializedData = file_get_contents('msgpack.data');
$object = msgpack_unpack($serializedData);

実行すると、デストラクタが呼び出されたことがわかる。

$ php msgpack_read.php
実行

シリアライズされたデータではFooというクラスである、という情報しか持っていない。従って、読み込む際にそのクラスの定義が、シリアライズされたものと異なっていてもデシリアライズできてしまう。
例えばこうしてみよう

class Foo {
    public function __destruct()
    {
        echo 42;
    }
};
$serializedData = file_get_contents('msgpack.data');
$object = msgpack_unpack($serializedData);

実行するとこうなる。

$ php msgpack_read.php
42

msgpack-phpの独自仕様を使わないようにする方法

PHPの間でやりとりしている分にはいいのだが、PHP以外の言語とmsgpackでやりとりすると問題になる。このような動作をmsgpack-phpに認めない場合は、msgpack.php_onlyを0に設定しよう。
それにより、msgpack-phpはこのようなデータを処理しなくなる

$ php -d msgpack.php_only=0 msgpack_read.php
PHP Warning:  [msgpack] (msgpack_unserialize_map_item) illegal key type in /home/mikochi/mycode/msgpack_read.php on line 9

また、stdClassやObject型をシリアライズする際にphp_onlyの設定により異なった値が生成される。

<?php
file_put_contents('serialized.data', msgpack_pack((object)[1,2,3]));

生成されたデータの中身を見てみよう

php_only =1

00000000: 84c0 a873 7464 436c 6173 73a1 3001 a131  ...stdClass.0..1
00000010: 02a1 3203                                ..2.

php_only = 0

00000000: 9301 0203                                ....

msgpack-phpの独自拡張

シリアライズされたデータを覗いてみよう。

00000000: 83c0 a873 7464 436c 6173 73a5 636f 756e  ...stdClass.coun
00000010: 7401 a56c 6162 656c a673 616d 706c 65    t..label.sample
format name
0x83 fixmap
0xc0 nil
0xa8 fixstr

msgpackのフォーマットに従うと、キーがnil、値がstringである。これはmsgpack-phpではこの箇所で扱われる。
fixmapはそれぞれ

key value
nil stdClass
count 1
label sample

となり、stdClassのオブジェクト、countの値は1, labelの値はsampleとしてデシリアライズされる。

Discussion