2024 年 PHP Conference Japan

unserialize

(PHP 4, PHP 5, PHP 7, PHP 8)

unserialize 從儲存的表示法建立 PHP 值

說明

unserialize(字串 $data, 陣列 $options = []): 混合

unserialize() 接受單一序列化變數,並將其轉換回 PHP 值。

警告

無論 options 值的 allowed_classes 為何,都不要將不受信任的使用者輸入傳遞給 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,可以透過將 max_depth 設定為 0 來禁用。

回傳值

返回轉換後的值,可以是 布林值整數浮點數字串陣列物件

如果傳遞的字串無法反序列化,則返回 false 並發出 E_WARNING

錯誤/例外

物件可能會在其反序列化處理程式中拋出 Throwable

更新日誌

版本 說明
8.3.0 現在,當輸入字串有未使用的資料時,會發出 E_WARNING
8.3.0 現在,當傳遞的字串無法反序列化時,會發出 E_WARNING;以前會發出 E_NOTICE
7.4.0 新增了 optionsmax_depth 元素,以設定反序列化期間允許的結構最大深度。
7.1.0 現在,optionsallowed_classes 元素是嚴格類型化的,也就是說,如果給定的值不是 陣列布林值unserialize() 會回傳 false 並發出 E_WARNING 警告。

範例

範例 #1 unserialize() 範例

<?php
// 這裡,我們使用 unserialize() 將從資料庫選取的字串中的工作階段資料
// 載入到 $session_data 陣列。
// 此範例補充了 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'); // 設定您的 callback_function

function mycallback($classname)
{
// 只需引入包含您的類別定義的檔案
// 您會得到 $classname 來判斷需要哪個類別定義
}
?>

注意事項

警告

發生錯誤時以及反序列化序列化後的 false 值時,都會回傳 false。可以透過將 dataserialize(false) 進行比較,或是透過捕捉發出的 E_NOTICE 通知來捕捉這個特殊情況。

參見

新增註釋

使用者貢獻的註釋 23 則註釋

me+phpnet at unreal4u dot com
7 年前
一些提醒,或許能幫某些人節省一些關於 `$options` 陣列的時間

假設您想要安全起見,不允許反序列化任何物件... 我的第一個想法是這樣做

<?php
$lol
= unserialize($string, false);
// 這會產生:
// 警告:unserialize() 預期參數 2 為陣列,但給定布林值
?>

正確的做法如下
<?php
$lol
= unserialize($string, ['allowed_classes' => false]);
?>

希望這能幫助到某些人!
karsten at dambekalns dot de
4 年前
請記住,allowed_classes 不使用繼承,也就是說,不允許介面,子類別也無法通過檢查。請參閱 https://3v4l.org/tdHfl
ErnestV
11 年前
僅供參考 - 如果序列化字串包含對無法實例化類別的引用(例如抽象類別),PHP 將立即因致命錯誤而終止。如果在 unserialize() 陳述式前面加上「@」以避免在日誌中充斥警告或注意事項,則將完全沒有線索可以找出腳本停止運作的原因。這讓我花了好幾個小時...
daniel at fourstaples dot com
14 年前
這是一個簡單的函式,用於取得序列化字串的類別(也就是反序列化後將返回的物件類型)

<?php
函數 get_serial_class($serial) {
$types = 陣列('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], '"');
}
?>

當我將序列化物件儲存到 Cookie 時,我會使用這個函數,以確保在反序列化時它是正確的類型。

類型名稱的格式/大小寫與使用 var_dump() 輸出的結果相同。
hadley8899 at gmail dot com
4 年前
對於遇到以下錯誤訊息的人:

PHP 注意:unserialize():錯誤,位元組 285 的偏移量 191 處發生錯誤,位於...

並且是從資料庫取得資料,請確保資料庫設定了正確的編碼,我的資料庫設定為 latin1_swedish_ci,所有資料看起來都很完美,實際上,當我將它複製到線上反序列化工具時,它可以正常運作。我將排序規則改為 utf8mb4_unicode_ci,一切就正常了。
bjd
7 年前
關於利用 PHP7 Unserialize 的討論,請參考:https://media.ccc.de/v/33c3-7858-exploiting_php7_unserialize
chris at pollett dot org
9 年前
當您序列化特定命名空間中的類別物件時,命名空間會記錄為序列化的一部分。如果您決定更改此命名空間的名稱,則可能難以讀取舊的序列化物件。例如,假設您序列化了一個 foo\A 類型的物件,您將專案的命名空間更改為 goo,但類別 A 的定義保持不變。您希望能夠將物件反序列化為 goo\A,而不是反序列化只會建立部分物件。要在類別定義中沒有巢狀物件的情況下解決此問題,您可以使用以下簡單的重新命名函數
/**
* 用於更改序列化 PHP 物件的命名空間(假設沒有
* 巢狀子物件)
*
* @param string $class_name 具有命名空間的新完整合格名稱
* @param string $object_string 序列化物件
*
* @return string 具有新名稱的序列化物件
*/
函數 renameSerializedObject($class_name, $object_string)
{
/* 物件名稱長度的位數需要
少於 12 位數(可能更像是 4 位數)才能正常運作。
*/
$name_length = intval(substr($object_string, 2, 14));
$name_space_info_length = strlen("O:".$name_length.":") +
$name_length + 2; // 2 個用於引號;
$object_string = 'O:' .
strlen($class_name) . ':"'. $class_name.'"' .
substr($object_string, $name_space_info_length);
return $object_string;
}
Ray.Paseur often uses Gmail
11 年前
在「類別與物件」文件中,有這麼一段:為了能夠 unserialize() 一個物件,該物件的類別必須被定義。

在 PHP 5.3 之前,這不是問題。但在 PHP 5.3 之後,由 SimpleXML_Load_String() 建立的物件無法被序列化。嘗試這樣做將導致執行階段錯誤,拋出異常。如果您將這樣的物件儲存在 $_SESSION 中,您會在執行後收到一個錯誤訊息,內容如下:

致命錯誤:未捕獲的異常 'Exception',訊息為 'Serialization of 'SimpleXMLElement' is not allowed',位於 [no active file]:0 堆疊追蹤:#0 {main} 拋出於 [no active file] 的第 0 行

整個 session 的內容都將遺失。希望這能幫人省點時間!

<?php // RAY_temp_ser.php
error_reporting(E_ALL);
session_start();
var_dump($_SESSION);
$_SESSION['hello'] = 'World';
var_dump($_SESSION);

// XML 字串測試資料
$xml = '<?xml version="1.0"?>
<families>
<parent>
<child index="1" value="Category 1">Child One</child>
</parent>
</families>'
;

// 建立一個物件 (類型為 SimpleXMLElement)
$obj = SimpleXML_Load_String($xml);

// 將物件儲存在 session 中
$_SESSION['obj'] = $obj;
chris AT cmbuckley DOT co DOT uk
16 年前
如註釋中所述,unserialize 在發生錯誤或布林值為 false 時會返回 false。這是第一個提到的解決方案,沒有使用錯誤處理。

<?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)
?>
m.m.j.kronenburg
8 年前
您可以使用以下程式碼在 PHP 5.3 及以上版本中使用 PHP 7 的 unserialize 函式。這會加入 $option 參數。

<?php

namespace
{

/**
* 適用於 PHP 5.3 以上版本的 PHP 7 unserialize 函式。
* 新增了 $option 參數 (allowed_classes)。
* 更多細節請參考 PHP unserialize 手冊。
**/
function php7_unserialize($str, $options = array())
{
if(
version_compare(PHP_VERSION, '7.0.0', '>='))
{ return
unserialize($str, $options); }

$allowed_classes = isset($options['allowed_classes']) ?
$options['allowed_classes'] : true;
if(
is_array($allowed_classes) || !$allowed_classes)
{
$str = preg_replace_callback(
'/(?=^|:)(O|C):\d+:"([^"]*)":(\d+):{/',
function(
$matches) use ($allowed_classes)
{
if(
is_array($allowed_classes) &&
in_array($matches[2], $allowed_classes))
{ return
$matches[0]; }
else
{
return
$matches[1].':22:"__PHP_Incomplete_Class":'.
(
$matches[3] + 1).
':{s:27:"__PHP_Incomplete_Class_Name";'.
serialize($matches[2]);
}
},
$str
);
}
unset(
$allowed_classes);
return
unserialize($str);
}

}
// namespace

namespace my_name_space
{
/**
* 在您的命名空間中使用新的 php7 unserialize,而無需
* 將所有 unserialize(...) 函式呼叫重新命名為
* php7_unserialize(...)。
**/
function unserialize($str, $options = array())
{ return
php7_unserialize($str, $options); }
}

?>
arbie samong
15 年前
__PHP_Incomplete_Class 物件解密

1. 首先要注意輸出。一個簡單的例子

__PHP_Incomplete_Class 物件 (
[__PHP_Incomplete_Class_Name] => SomeObject1
[obj1property1] => somevalue1 [obj1property2] => __PHP_Incomplete_Class 物件 ( [__PHP_Incomplete_Class_Name] => SomeObject2 [obj2property1] => somevalue1 [obj2property2] => 陣列 (
['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )

2. 我們分析並分解它。
__PHP_Incomplete_Class 物件告訴您有一個需要以某種方式宣告的物件。
__PHP_Incomplete_Class_Name 只是告訴您預期的類別名稱。目前它只是一個屬性。

所以我們有
a) 一個未知物件,其類別名稱為 SomeObject1(第一個類別)
b) 它有兩個屬性,分別是 obj1property1 和 obj2property2
c) obj2property2 本身是一個物件,其類別名稱為 SomeObject2(第二個類別)
d) SomeObject2 有兩個屬性,obj2property1 和 obj2property2
e) obj2property2 是一個包含兩個元素的陣列

3. 現在我們已經了解了結構,我們將基於它創建類別定義。我們現在只創建屬性,方法並非必要。

<?php
class SomeObject1 {
public
$obj1property1;
public
$obj1property2;
}
class
SomeObject2 {
public
$obj2property1;
public
$obj2property2;
}
?>

4. 將其引入你的腳本,就能解決輸出的 `__PHP_Incomplete_Class Object` 問題。現在你將擁有

SomeObject1 ( [obj1property1] => somevalue1 [obj1property2] => SomeObject2 ( [obj2property1] => somevalue1 [obj2property2] => Array ( ['key1'] => somevalue3, ['key2'] => somevalue4 ) ) )

如你所見,`__PHP_Incomplete_Class Object` 消失了,取而代之的是類別名稱。屬性 `__PHP_Incomplete_Class_Name` 也被移除。

5. 至於陣列屬性 obj2property2,我們可以直接訪問它,並假設它是一個陣列,然後遍歷它

<?php

// 這將是 SomeObject1
$data = unserialize($serialized_data);

// 這將是 SomeObject2
$data2 = $data->obj1property2;

foreach(
$data2->obj2property2 as $key => $value):
print
$key.' : '. $value .'<br>';
endforeach;

?>

輸出
key1 : somevalue3
key2 : somevalue4

這樣就完成了。你可以在類別宣告中為給定的屬性添加更多方法,只要你保持原始輸出作為資料類型的基礎。
BenBE at omorphia dot de
17 年前
當嘗試序列化或反序列化遞迴陣列或其他鏈接數據時,你可能會發現未記載的 R 資料類型非常有用。

如果你想要一個像這樣產生的陣列
<?
$a = array();
$a[0] =& $a;
?>
序列化後,你可以使用類似這樣的字串來儲存它
<?
$a = unserialize("a:1:{i:0;R:1;}");
?>

這兩個來源都會使 $a 持有一個在其索引 0 中自我引用的陣列。

R 的參數是從 1 開始的序列化字串的已創建子變數的索引。
chris at colourlovers dot com
13 年前
任何在序列化包含 SimpleXMLElement 物件的資料時遇到問題的人,請查看這個

這段程式碼會遍歷 $data 尋找所有 SimpleXMLElement 的子元素,並對它們執行 ->asXML() 方法,將它們轉換為字串,使其可以序列化。其他資料則保持不變。

<?php
function exportNestedSimpleXML($data) {
if (
is_scalar($data) === false) {
foreach (
$data as $k => $v) {
if (
$v instanceof SimpleXMLElement) {
$v = str_replace("&#13;","\r",$v->asXML());
} else {
$v = exportNestedSimpleXML($v);
}

if (
is_array($data)) {
$data[$k] = $v;
} else if (
is_object($data)) {
$data->$k = $v;
}
}
}

return
$data;
}

$data = array (
"baz" => array (
"foo" => new stdClass(),
"int" => 123,
"str" => "asdf",
"bar" => new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>'),
)
);

var_dump($data);
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
object(SimpleXMLElement)#4 (1) {
[0]=>
string(3) "bar"
}
}
}*/

var_dump(exportNestedSimpleXML($data));
/*array(1) {
["baz"]=>
array(4) {
["foo"]=>
object(stdClass)#3 (0) {
}
["int"]=>
int(123)
["str"]=>
string(4) "asdf"
["bar"]=>
string(54) "<?xml version="1.0" encoding="UTF-8"?>
<foo>bar</foo>
"
}
}
*/
?>
double at dumpit dot com
17 年前
這個小函式會檢查序列化後的字串是否格式正確。

PHP < 6 版本適用,因為我聽說 PHP 內部函式在這個版本之後會有變動,
也許可以很容易地修改它來適應。

<?php

function wd_check_serialization( $string, &$errmsg )
{

$str = 's';
$array = 'a';
$integer = 'i';
$any = '[^}]*?';
$count = '\d+';
$content = '"(?:\\\";|.)*?";';
$open_tag = '\{';
$close_tag = '\}';
$parameter = "($str|$array|$integer|$any):($count)" . "(?:[:]($open_tag|$content)|[;])";
$preg = "/$parameter|($close_tag)/";
if( !
preg_match_all( $preg, $string, $matches ) )
{
$errmsg = 'not a serialized string';
return
false;
}
$open_arrays = 0;
foreach(
$matches[1] AS $key => $value )
{
if( !empty(
$value ) && ( $value != $array xor $value != $str xor $value != $integer ) )
{
$errmsg = 'undefined datatype';
return
false;
}
if(
$value == $array )
{
$open_arrays++;
if(
$matches[3][$key] != '{' )
{
$errmsg = 'open tag expected';
return
false;
}
}
if(
$value == '' )
{
if(
$matches[4][$key] != '}' )
{
$errmsg = 'close tag expected';
return
false;
}
$open_arrays--;
}
if(
$value == $str )
{
$aVar = ltrim( $matches[3][$key], '"' );
$aVar = rtrim( $aVar, '";' );
if(
strlen( $aVar ) != $matches[2][$key] )
{
$errmsg = 'stringlen for string not match';
return
false;
}
}
if(
$value == $integer )
{
if( !empty(
$matches[3][$key] ) )
{
$errmsg = 'unexpected data';
return
false;
}
if( !
is_integer( (int)$matches[2][$key] ) )
{
$errmsg = 'integer expected';
return
false;
}
}
}
if(
$open_arrays != 0 )
{
$errmsg = 'wrong setted arrays';
return
false;
}
return
true;
}

?>
Are Pedersen
18 年前
請注意,如果在同時擁有 32 位元和 64 位元伺服器的伺服器陣列中使用 serialize/unserialize,可能會得到意想不到的結果。

例如:如果您在 64 位元系統上序列化一個值為 2147483648 的整數,然後在 32 位元系統上反序列化它,您將得到 -2147483648 的值。這是因為 32 位元上的整數不能超過 2147483647,所以它會溢位。
w dot laurencine at teknoa dot net
15 年前
當處理包含 "\r" 的字串時,字串長度似乎無法正確計算。以下方法解決了我的問題

<?php
// 從 $unserialized 字串中移除 \r 字元
$unserialized = str_replace("\r","",$unserialized);

// 然後再 unserialize()
unserialize($unserialized);
?>
suman dot jis at gmail dot com
12 年前
我遇到了 unserialize() 偏移錯誤。

如果您遇到類似的問題,請使用以下步驟

$auctionDetails = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $dataArr[$i]['auction_details'] );
$auctionDetails = unserialize($auctionDetails);
匿名
21 天前
請注意,PHP 7 和 8 在 unserialize() 的運作方式上略有不同。

在 PHP 8 中,未去除空白的字串會發出類似以下的警告:

PHP 警告:unserialize(): 額外資料從 722 位元組中的偏移量 721 開始,位於 /tmp/a.php 的第 4 行

所以像這樣

$s = 's:3:"bar";'."\n"
unserialize($s); # 發出警告
unserialize(trim($s)); # 沒有警告
Chris Hayes (chris at hypersites dot com)
20 年前
回覆先前關於必須在使用 unserialize 之前包含物件定義的文章。有一個解決方法。

當一個物件被序列化時,字串的第一部分實際上是類別的名稱。當一個未知的物件被反序列化時,這會被保留為一個屬性。因此,如果您再次序列化它,您將獲得與序列化原始物件時完全相同的字串。基本上,簡而言之...

如果您使用

$_SESSION['my_object'] = unserialize(serialize($_SESSION['my_object']))

那麼您將獲得正確類型的物件,即使 session 最初將其載入為 stdClass 類型的物件。
OscarZarrus
2 年前
對於那些正在尋找有效解決方案來處理有爭議的「FALSE」的人,他們可以使用這個函式,在無法反序列化的字串的情況下,它會拋出異常而不是「FALSE」。反之亦然,它會返回反序列化的變數。
<?php
/**
* @param string $serializedString
* @param array $options
* @return mixed
* @throws Exception
*/
function UnSerialize(string $serializedString, array $options = []) {
$_unserialized = @unserialize($serializedString, $options);
if (
$serializedString === serialize(false) || $_unserialized !== false){
return
$_unserialized;
}
throw new
Exception("無法反序列化的字串");

}

?>
aderyn at nowhere dot tld
21 年前
快速提醒
如果您將序列化物件儲存在一個 session 中,您必須在初始化 (session_start()) session _之前_ 引入該類別。
匿名
5 年前
如果 serialize() 是答案,那麼你幾乎可以肯定問錯了問題。

JSON 已被廣泛使用。它唯一沒做的事情,正是讓序列化極度危險的事情。只需要一個狡猾的駭客將精心設計的有效負載傳遞給一個所謂「安全的」序列化呼叫,例如,就可以用惡意程式碼覆蓋資料庫驅動程式。

重新建立物件。正常地。使用實際資料和原始程式碼檔案,而不是使用 serialize。否則就是懶惰,近乎惡意。
MBa
13 年前
檢查字串是否已序列化

$blSerialized=(@unserialize($sText)||$sText=='b:0;');
To Top