php序列化serialize() 與還原序列化unserialize()

來源:互聯網
上載者:User

把複雜的資料類型壓縮到一個字串中
 
serialize() 把變數和它們的值編碼成文本形式
 
unserialize() 恢複原先變數
 
eg:

 代碼如下 複製代碼

$stooges = array('Moe','Larry','Curly');
$new = serialize($stooges);
print_r($new);
echo "<br />";
print_r(unserialize($new));

結果:
a:3:{i:0;s:3:"Moe";i:1;s:5:"Larry";i:2;s:5:"Curly";}
Array ( [0] => Moe [1] => Larry [2] => Curly )

當把這些序列化的資料放在URL中在頁面之間會傳遞時,需要對這些資料調用urlencode(),以確保在其中的URL元字元進行處理:
 

 代碼如下 複製代碼

$shopping = array('Poppy seed bagel' => 2,'Plain Bagel' =>1,'Lox' =>4);

echo '<a href="next.php?cart='.urlencode(serialize($shopping)).'">next</a>';

margic_quotes_gpc和magic_quotes_runtime配置項的設定會影響傳遞到unserialize()中的資料。
 
如果magic_quotes_gpc項是啟用的,那麼在URL、POST變數以及cookies中傳遞的資料在還原序列化之前必須用stripslashes()進行處理:
 

 代碼如下 複製代碼

 

$new_cart = unserialize(stripslashes($cart));

//如果magic_quotes_gpc開啟
$new_cart = unserialize($cart);

如果magic_quotes_runtime是啟用的,那麼在向檔案中寫入序列化的資料之前必須用addslashes()進行處理,而在讀取它們之前則必須用stripslashes()進行處理:
 

 代碼如下 複製代碼

$fp = fopen('/tmp/cart','w');

fputs($fp,addslashes(serialize($a)));

fclose($fp);

//如果magic_quotes_runtime開啟
$new_cat = unserialize(stripslashes(file_get_contents('/tmp/cart')));

//如果magic_quotes_runtime關閉
$new_cat = unserialize(file_get_contents('/tmp/cart'));

在啟用了magic_quotes_runtime的情況下,從資料庫中讀取序列化的資料也必須經過stripslashes()的處理,儲存到資料庫中的序列化資料必須要經過addslashes()的處理,以便能夠適當地儲存。

 代碼如下 複製代碼

mysql_query("insert into cart(id,data) values(1,'".addslashes(serialize($cart))."')");

$rs = mysql_query('select data from cart where id=1');

$ob = mysql_fetch_object($rs);

//如果magic_quotes_runtime開啟
$new_cart = unserialize(stripslashes($ob->data));

//如果magic_quotes_runtime關閉
$new_cart = unserialize($ob->data);

當對一個對象進行還原序列化操作時,PHP會自動地調用其__wakeUp()方法。這樣就使得對象能夠重建立立起序列化時未能保留的各種狀態。例如:資料庫連接等。

用例子給你說明一下

 代碼如下 複製代碼

<?php

//聲明一個類

class dog {

var $name;

var $age;

var $owner;

function dog($in_name="unnamed",$in_age="0",$in_owner="unknown") {

$this->name = $in_name;

$this->age = $in_age;

$this->owner = $in_owner;

}

function getage() {

return ($this->age * 365);

}

function getowner() {

return ($this->owner);

}

function getname() {

return ($this->name);

}

}

//執行個體化這個類

$ourfirstdog = new dog("Rover",12,"Lisa and Graham");

//用serialize函數將這個執行個體轉化為一個序列化的字串

$dogdisc = serialize($ourfirstdog);

print $dogdisc; //$ourfirstdog 已經序列化為字串 O:3:"dog":3:{s:4:"name";s:5:"Rover";s:3:"age";i:12;s:5:"owner";s:15:"Lisa and Graham";}

/*

-----------------------------------------------------------------------------------------

在這裡你可以將字串 $dogdisc 儲存到任何地方如 session,cookie,資料庫,php檔案

-----------------------------------------------------------------------------------------

*/

//我們在此登出這個類

unset($ourfirstdog);

?>

b.php

<?php

 

?>

<?php

//聲明一個類

class dog {

var $name;

var $age;

var $owner;

function dog($in_name="unnamed",$in_age="0",$in_owner="unknown") {

$this->name = $in_name;

$this->age = $in_age;

$this->owner = $in_owner;

}

function getage() {

return ($this->age * 365);

}

function getowner() {

return ($this->owner);

}

function getname() {

return ($this->name);

}

}

/*還原作業   */

/*

-----------------------------------------------------------------------------------------

在這裡將字串 $dogdisc 從你儲存的地方讀出來如 session,cookie,資料庫,php檔案

-----------------------------------------------------------------------------------------

*/

$dogdisc='O:3:"dog":3:{s:4:"name";s:5:"Rover";s:3:"age";i:12;s:5:"owner";s:15:"Lisa and Graham";}';

//我們在這裡用 unserialize() 還原已經序列化的對象

$pet = unserialize($dogdisc); //此時的 $pet 已經是前面的 $ourfirstdog 對象了

//獲得年齡和名字屬性

$old = $pet->getage();

$name = $pet->getname();

//這個類此時無需執行個體化可以繼續使用,而且屬性和值都是保持在序列化之前的狀態

print "Our first dog is called $name and is $old days old<br>";

?>

序列化與還原序列化文法解析不一致帶來的安全隱患

. PHP string serialize() 相關源碼分析
------------------------------------

 代碼如下 複製代碼

static inline void php_var_serialize_string(smart_str *buf, char *str, int len) /* {{{ */
  {
smart_str_appendl(buf, "s:", 2);
smart_str_append_long(buf, len);
smart_str_appendl(buf, ":\"", 2);
smart_str_appendl(buf, str, len);
smart_str_appendl(buf, "\";", 2);
  }

 


通過上面的程式碼片段可以看到 serialize() 對 string 序列化處理方式如下:

 

 代碼如下 複製代碼
$str = 'ryatsyne';
  var_dump(serialize($str));
  // $str serialized string output
  // s:8:"ryatsyne";

ii. PHP string unserialize() 相關源碼分析
---------------------------------------

unserialize() 函數對 string 的還原序列化則分為兩種,一種是對 `s:` 格式的序列化 string 進行處理:

 代碼如下 複製代碼

switch (yych) {
  ...
case 's':  goto yy9;
...
  yy9:
yych = *(YYMARKER = ++YYCURSOR);
if (yych == ':') goto yy46;
goto yy3;
...
  yy46:
yych = *++YYCURSOR;
if (yych == '+') goto yy47;
if (yych <= '/') goto yy18;
if (yych <= '9') goto yy48;
goto yy18;
  yy47:
yych = *++YYCURSOR;
if (yych <= '/') goto yy18;
if (yych >= ':') goto yy18;
  yy48:
++YYCURSOR;
if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
yych = *YYCURSOR;
if (yych <= '/') goto yy18;
if (yych <= '9') goto yy48;
if (yych >= ';') goto yy18;
yych = *++YYCURSOR;
if (yych != '"') goto yy18;
++YYCURSOR;
{
size_t len, maxlen;
char *str;
 
len = parse_uiv(start + 2);
maxlen = max - YYCURSOR;
if (maxlen < len) {
  *p = start + 2;
  return 0;
}
 
str = (char*)YYCURSOR;
 
YYCURSOR += len;
 
if (*(YYCURSOR) != '"') {
  *p = YYCURSOR;
  return 0;
}
  // 確保格式為 s:x:"x"
 
YYCURSOR += 2;
*p = YYCURSOR;
  // 注意這裡,*p 指標直接後移了兩位,也就是說沒有判斷 " 後面是否為 ;
 
INIT_PZVAL(*rval);
ZVAL_STRINGL(*rval, str, len, 1);
return 1;

 

另一種是對 S: 格式的序列 string 進行處理(此格式在 serialize() 函數序列化處理中並沒有定義):

 代碼如下 複製代碼

static char *unserialize_str(const unsigned char **p, size_t *len, size_t maxlen)
  {
size_t i, j;
char *str = safe_emalloc(*len, 1, 1);
unsigned char *end = *(unsigned char **)p+maxlen;
 
if (end < *p) {
  efree(str);
  return NULL;
}
 
for (i = 0; i < *len; i++) {
  if (*p >= end) {
efree(str);
return NULL;
  }
  if (**p != '\\') {
str[i] = (char)**p;
  } else {
unsigned char ch = 0;
 
for (j = 0; j < 2; j++) {
  (*p)++;
  if (**p >= '0' && **p <= '9') {
ch = (ch << 4) + (**p -'0');
  } else if (**p >= 'a' && **p <= 'f') {
ch = (ch << 4) + (**p -'a'+10);
  } else if (**p >= 'A' && **p <= 'F') {
ch = (ch << 4) + (**p -'A'+10);
  } else {
efree(str);
return NULL;
  }
}
str[i] = (char)ch;
  }
  (*p)++;
}
str[i] = 0;
*len = i;
return str;
  }
  // 上面的函數是對 \72\79\61\74\73\79\6e\65 這樣十六進位形式字串進行轉換
  ...
  switch (yych) {
...
case 'S':  goto yy10;
// 處理過程與 s: 相同
if ((str = unserialize_str(&YYCURSOR, &len, maxlen)) == NULL) {
  return 0;
}
// 處理過程與 s: 相同

 

從上面的程式碼片段可以看到 unserialize() 對序列化後的 string 還原序列化處理如下:

 代碼如下 複製代碼

$str1 = 's:8:"ryatsyne";';
  $str2 = 's:8:"ryatsyne"t';
  $str3 = 'S:8:"\72\79\61\74\73\79\6e\65"';
  var_dump(unserialize($str));
  // $str1, $str2 and $str3 unserialized string output
  // ryatsyne;

 

iii. 文法解析處理不一致導致的安全隱患
-----------------------------

從上述分析過程可以看到 PHP 在還原序列化 string 時沒有嚴格按照序列化格式 s:x:"x"; 進行處理,沒有對 " 後面的是否存在 ; 進行判斷,同時增加了對十六進位形式字串的處理,這樣前後處理的不一致讓人很費解,同時由於 PHP 手冊中對此沒有詳細的說明,大部分程式員對此處理過程並不瞭解,這可能導致其在編碼過程中出現疏漏,甚至導致嚴重的安全問題。

回到文章開頭提到的 IPB 漏洞上,利用這個 funny feature of PHP 可以很容易的 bypass safeUnserialize() 函數的過濾:)

 代碼如下 複製代碼

* mixed safe_unserialize(string $serialized)
* Safely unserialize, that is only unserialize string, numbers and arrays, not objects
*
* @license Public Domain
* @author dcz (at) phpbb-seo (dot) com
*/
static public function safeUnserialize( $serialized )
{
// unserialize will return false for object declared with small cap o
// as well as if there is any ws between O and :
if ( is_string( $serialized ) && strpos( $serialized, "\0" ) === false )
{
if ( strpos( $serialized, 'O:' ) === false )
{
// the easy case, nothing to worry about
// let unserialize do the job
return @unserialize( $serialized );
}
else if ( ! preg_match('/(^|;|{|})O:[+\-0-9]+:"/', $serialized ) )
{
// in case we did have a string with O: in it,
// but it was not a true serialized object
return @unserialize( $serialized );
}
}

return false;
}
 
// a:1:{s:8:"ryatsyne"tO:8:"ryatsyne":0:{}}
// 只要構造類似的序列化字串就可以輕易突破這裡的過濾了

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.