PHP序列化以及還原序列化系列[1]--PHP序列化格式的寫法

來源:互聯網
上載者:User

標籤:nothing   foo   head   自身   data   第一個   echo   cas   new t   

還原序列化:對單一的已序列化的變數進行操作,將其轉換回 PHP 的值(zval)。

PHP序列化方式

PHP在序列化的時候會將相應的變數以對應的索引值進行儲存。

將一個類序列化的話,處理代碼主要的 檔案:ext/standard/var.c 中,如下。

php_var_serialize_class()函數:

static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, HashTable *var_hash TSRMLS_DC) /* {{{ */ { ... incomplete_class = php_var_serialize_class_name(buf, struc TSRMLS_CC); ...

php_var_serialize_class_name()函數:

static inline zend_bool php_var_serialize_class_name(smart_str *buf, zval *struc TSRMLS_DC) /* {{{ */ { PHP_CLASS_ATTRIBUTES; PHP_SET_CLASS_ATTRIBUTES(struc); smart_str_appendl(buf, "O:", 2); smart_str_append_long(buf, (int)name_len); smart_str_appendl(buf, ":\"", 2); smart_str_appendl(buf, class_name, name_len); smart_str_appendl(buf, "\":", 2); PHP_CLEANUP_CLASS_ATTRIBUTES(); return incomplete_class; }

需要序列化一個類的話,首先PHP會先將類名序列化。格式為

O:類名長度:"類名":值:{} <?php class test { public function show_one() { echo $this->one; } public function show_two() { echo "123"; } }

例:如果一個類名叫做 test 的類沒有定義任何變數的話,序列化之後結果如下:

O:4:"test":0:{}

我們可以看到,這個類中的方法沒有在序列化字串中出現,也體現了開頭的“序列化一個對象將會儲存對象的所有變數,但是不會儲存對象的方法,只會儲存類的名字。”。

其中還有比較特殊的序列化就是 數組中的引用(&) 的序列化與執行個體化後對象中自身的二次賦值。

我們在這用PHP Internal Book中的例子。

例1:

<?php /** * User: LonelyRain * Date: 16/9/14 * Time: 下午12:53 */ $a = ["foo"]; $a[1] =& $a[0]; $s = serialize($a); print $s;

以上代碼的序列化結果是

a:2:{i:0;s:3:"foo";i:1;R:2;}

這裡的 R:2; 部分意味著"指向第二個值".什麼是第二個值?整個數組代表第一個值, (s:3:"foo") 代表第二個值.

<?php /** * User: LonelyRain * Date: 16/9/14 * Time: 下午12:53 */ $o = new stdClass; $o->foo = $o; $s = serialize($o); print $s;

以上代碼的序列化結果是

O:8:"stdClass":1:{s:3:"foo";r:1;}

以下是zval對應的類型和鍵對照表

序列化鍵名對照表: 數組中二次賦值(&): R; 對象二次賦值 : r; NULL : N; true : b:1; false : b:0; Long : i; Double : d; String : s/S; Class : C; Array : a; Object : O;

變數不同的屬性也有著不同的格式

public : key; protected : \0*\0key; private : \0key\0;

通過執行個體來觀察:

<?php /** * User: LonelyRain * Date: 16/9/14 * Time: 下午12:53 */ class Test { public $public = 1; protected $protected = 2; private $private = 3; } $a = new Test(); $s = serialize($a); var_dump($s);

結果:

"O:4:"Test":3:{s:6:"public";i:1;s:12:"*protected";i:2;s:13:"Testprivate";i:3;}"

再來看一看還原序列化的相關知識。大家應該注意到了String對應著兩個鍵,s與S。

serialize()與unserialize()處理有著一些差異。PHP源碼serialize()中是沒有相關序列化是以S為標識的,但是在unserialize中又有對S鍵的相關處理,下面我把相關部分代碼貼出來供讀者參考。

case ‘S‘: goto yy10; ... yy10: yych = *(YYMARKER = ++YYCURSOR); if (yych == ‘:‘) goto yy39; goto yy3; ... yy39: yych = *++YYCURSOR; if (yych == ‘+‘) goto yy40; if (yych <= ‘/‘) goto yy18; if (yych <= ‘9‘) goto yy41; goto yy18; 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; ... ...

如果大家繼續看接下去的代碼下去,會發現s和S的就會發現兩個鍵的處理方式是一模一樣的。

如果大家看了phpcodz 10,裡面寫道 a:1:{s:8:"ryatsyne"tO:8:"ryatsyne":0:{}} 這樣可以突破

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; }

這個payload在php5.6.23中失效,看以下代碼

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; } if (*(YYCURSOR + 1) != ‘;‘) { *p = YYCURSOR + 1; return 0; } YYCURSOR += 2; *p = YYCURSOR; INIT_PZVAL(*rval); ZVAL_STRINGL(*rval, str, len, 1); return 1; }

代碼中已經多加了分號符號校正,這個tricky在這個php版本中是無效的。

if (*(YYCURSOR + 1) != ‘;‘) { *p = YYCURSOR + 1; return 0; } WDDX序列化方式

序列化本質就是將程式的值以相應的格式儲存下來,所以我們不止單單可以用上面的serialize函數進行序列化。PHP還提供了另外一種序列化格式為Web分布式資料交換(WDDX)。WDDX是XML的子集,所以符合WDDX的序列化過後的字串格式是符合xml的規範的。

示範代碼:

<?php /** * User: LonelyRain * Date: 16/9/14 * Time: 下午12:53 */ $a = ["foo"]; $a[1] =& $a[0]; echo wddx_serialize_value($a); ?>

結果:

<wddxPacket version=‘1.0‘><header/><data><array length=‘2‘><string>foo</string><string>foo</string></array></data></wddxPacket>

可以看到才用wddx_serialize_value()函數處理的$a和之前使用serialize()函數處理的值都被儲存下來了,只不過遵守的格式有著相應的區別。

WDDX序列化還原序列化相關函數:

wddx_serialize_value: 將單一值連續化。 wddx_serialize_vars : 將多值連續化。 wddx_packet_start : 開始新的 WDDX 封包。 wddx_packet_end : 結束的 WDDX 封包。 wddx_add_vars : 將 WDDX 封包連續化。 wddx_deserialize : 將 WDDX 封包解連續化。

這一篇主要講了序列化後資料的格式,下一次會寫PHP序列化中一塊重要的內容,PHP的魔術方法等內容。

Reference:

PHP核心

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

PHP序列化以及還原序列化系列[1]--PHP序列化格式的寫法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.