標籤: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序列化格式的寫法