unserialize
(PHP 4, PHP 5, PHP 7, PHP 8)
unserialize — 保存用表現から PHP の値を生成する
説明
unserialize() は、シリアル化された変数を PHP の値に戻す変換(アンシリアライズ)を行います。
allowed_classes
の options
の値にかかわらず、
ユーザーからの入力をそのまま unserialize() に渡してはいけません。
アンシリアライズの時には、オブジェクトのインスタンス生成やオートローディングなどで
コードが実行されることがあり、悪意のあるユーザーがこれを悪用するかもしれないからです。
シリアル化したデータをユーザーに渡す必要がある場合は、安全で標準的なデータ交換フォーマットである
JSON などを使うようにしましょう。
json_decode() および json_encode()
を利用します。
外部に保存されているシリアル化されたデータをアンシリアライズする必要がある場合は、 hash_hmac() を使ったデータの検証を検討しましょう。 他者によるデータの改ざんがないことを確かめるためです。
パラメータ
data
-
シリアル化された文字列。
もしアンシリアライズする変数がオブジェクトの場合、 オブジェクトが無事再作成された後、PHP は自動的にメンバ関数 __unserialize() または __wakeup() (存在していれば) をコールしようとします。
注意: unserialize_callback_func ディレクティブ
unserialize_callback_func ディレクティブで指定したコールバックは、未定義のクラスをアンシリアライズしようとした場合にコールされます。 コールバックが指定されない場合は、__PHP_Incomplete_Class がインスタンス化されます。
options
-
unserialize() に連想配列で渡すオプション。
有効なオプション 名前 型 説明 allowed_classes
mixed 受け付けるクラス名の配列を指定します。あらゆるクラスを拒否する場合は false
、あらゆるクラスを受け付ける場合はtrue
を指定します。 このオプションを指定しているときに、もし unserialize() が受け付け対象外のクラスのオブジェクトに遭遇すると、 そのオブジェクトを __PHP_Incomplete_Class のインスタンスに変換します。 このオプションを省略すると、true
を指定したのと同じ意味になります。 つまり、PHP はあらゆるクラスのオブジェクトをインスタンス化しようとします。max_depth
int アンシリアライズ処理の間に許される、 データ構造の再帰の深さの最大値を設定します。 これは、スタックオーバーフローを防ぐためのものです。 デフォルトの深さの最大値は 4096
であり、0
に設定すると、 この制限を無効にすることができます。
エラー / 例外
オブジェクトは、 アンシリアライズを実行するハンドラで Throwable をスローしても構いません。
変更履歴
バージョン | 説明 |
---|---|
8.3.0 |
渡された文字列が復元できない場合、
E_WARNING が発生するようになりました。
これより前のバージョンでは、E_NOTICE
が発生していました。
|
7.4.0 |
options に max_depth が追加されました。
これは、アンシリアライズ処理の間に許される、
データ構造の再帰の深さの最大値を設定するものです。
|
7.1.0 |
options の allowed_classes 要素は、
型を厳密に調べるようになりました。
つまり、array または bool 以外の型が与えられると、
unserialize() 関数は false を返し、
E_WARNING レベルの警告を発生させます。
|
例
例1 unserialize() の例
<?php
// ここで、データベースから $session_data にセッションデータをロード
// するために unserialize() を使用します。
// この例は、serialize() で記述された例を補足するものです。
$conn = odbc_connect("webdb", "php", "chicken");
$stmt = odbc_prepare($conn, "SELECT data FROM sessions WHERE id = ?");
$sqldata = array($_SERVER['PHP_AUTH_USER']);
if (!odbc_execute($stmt, $sqldata) || !odbc_fetch_into($stmt, $tmp)) {
// 実行または取得が失敗した場合、空の配列で初期化します
$session_data = array();
} else {
// tmp[0] にシリアル化されたデータを保持している必要があります。
$session_data = unserialize($tmp[0]);
if (!is_array($session_data)) {
// 何か問題があったため、空の配列で初期化します。
$session_data = array();
}
}
?>
例2 unserialize_callback_func の例
<?php
$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';
ini_set('unserialize_callback_func', 'mycallback'); // 独自のコールバック関数を設定する
function mycallback($classname)
{
// クラスが定義されているファイルをインクルードするだけです。
// どのクラス定義が必要になるのかを $classname で判断します。
}
?>
注意
参考
- json_encode() - 値を JSON 形式にして返す
- json_decode() - JSON 文字列をデコードする
- hash_hmac() - HMAC 方式を使用してハッシュ値を生成する
- serialize() - 値の保存可能な表現を生成する
- クラスのオートローディング
- unserialize_callback_func
- unserialize_max_depth
- __wakeup()
- __serialize()
- __unserialize()
User Contributed Notes 23 notes
Just some reminder which may save somebody some time regarding the `$options` array:
Say you want to be on the safe side and not allow any objects to be unserialized... My first thought was doing the following:
<?php
$lol = unserialize($string, false);
// This will generate:
// Warning: unserialize() expects parameter 2 to be array, boolean given
?>
The correct way of doing this is the following:
<?php
$lol = unserialize($string, ['allowed_classes' => false]);
?>
Hope it helps somebody!
Keep in mind that the allowed_classes does not use inheritance, i.e. allowing an interface is not possible and sub-classes won't pass the check. See https://3v4l.org/tdHfl
Just a note - if the serialized string contains a reference to a class that cannot be instantiated (e.g. being abstract) PHP will immediately die with a fatal error. If the unserialize() statement is preceded with a '@' to avoid cluttering the logs with warns or notices there will be absolutely no clue as to why the script stopped working. Cost me a couple of hours...
Here's a simple function to get the class of a serialized string (that is, the type of object that will be returned if it's unserialized):
<?php
function get_serial_class($serial) {
$types = array('s' => 'string', 'a' => 'array', 'b' => 'bool', 'i' => 'int', 'd' => 'float', 'N;' => 'NULL');
$parts = explode(':', $serial, 4);
return isset($types[$parts[0]]) ? $types[$parts[0]] : trim($parts[2], '"');
}
?>
I use this when saving a serialized object to a cookie, to make sure it is the right type when I go to unserialize it.
The type names are the same format/case as you would see if you did a var_dump().
For the people who are getting the error
PHP Notice: unserialize(): Error at offset 191 of 285 bytes in ...
and are getting the data from a database, Make sure that you have the database set the the correct encoding, I had the database set as latin1_swedish_ci and all of the data looked perfect, Infact when i copied it into a online unserialize it worked fine. I changed the collation to utf8mb4_unicode_ci and all worked fine.
Talk on Exploiting PHP7 Unserialize here: https://media.ccc.de/v/33c3-7858-exploiting_php7_unserialize
When you serialize an object of a class from a particular namespace, the namespace is recorded as part of the serialization. If you decide to change this namespace's name, it can be hard to read in old serialized objects. I.e., suppose you had serialized an object of type foo\A, you change the namespace of your project to goo but otherwise leave the class definition of A unchanged. You would like to be able to unserialize the object as goo\A, instead unserialization will only create a partial object. To fix this in the case where you don't have nested objects in your class definition, you can use the following simple rename function:
/**
* Used to change the namespace of a serialized php object (assumes doesn't
* have nested subobjects)
*
* @param string $class_name new fully qualified name with namespace
* @param string $object_string serialized object
*
* @return string serialized object with new name
*/
function renameSerializedObject($class_name, $object_string)
{
/* number of digits in the length of name of the object needs to be
less than 12 digits (probably more like 4) for this to work.
*/
$name_length = intval(substr($object_string, 2, 14));
$name_space_info_length = strlen("O:".$name_length.":") +
$name_length + 2; // 2 for quotes;
$object_string = 'O:' .
strlen($class_name) . ':"'. $class_name.'"' .
substr($object_string, $name_space_info_length);
return $object_string;
}
In the Classes and Objects docs, there is this: In order to be able to unserialize() an object, the class of that object needs to be defined.
Prior to PHP 5.3, this was not an issue. But after PHP 5.3 an object made by SimpleXML_Load_String() cannot be serialized. An attempt to do so will result in a run-time failure, throwing an exception. If you store such an object in $_SESSION, you will get a post-execution error that says this:
Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SimpleXMLElement' is not allowed' in [no active file]:0 Stack trace: #0 {main} thrown in [no active file] on line 0
The entire contents of the session will be lost. Hope this saves someone some time!
<?php // RAY_temp_ser.php
error_reporting(E_ALL);
session_start();
var_dump($_SESSION);
$_SESSION['hello'] = 'World';
var_dump($_SESSION);
// AN XML STRING FOR TEST DATA
$xml = '<?xml version="1.0"?>
<families>
<parent>
<child index="1" value="Category 1">Child One</child>
</parent>
</families>';
// MAKE AN OBJECT (GIVES SimpleXMLElement)
$obj = SimpleXML_Load_String($xml);
// STORE THE OBJECT IN THE SESSION
$_SESSION['obj'] = $obj;
As mentioned in the notes, unserialize returns false in the event of an error and for boolean false. Here is the first solution mentioned, without using error handling:
<?php
function isSerialized($str) {
return ($str == serialize(false) || @unserialize($str) !== false);
}
var_dump(isSerialized('s:6:"foobar";')); // bool(true)
var_dump(isSerialized('foobar')); // bool(false)
var_dump(isSerialized('b:0;')); // bool(true)
?>