PHP序列化和反序列化語法差異問題
介紹
官方文檔中介紹PHP序列化和反序列化如下:
所有php里面的值都可以使用函數(shù)serialize()來返回一個(gè)包含字節(jié)流的字符串來表示。unserialize()函數(shù)能夠重新把字符串變回php原來的值。 序列化一個(gè)對(duì)象將會(huì)保存對(duì)象的所有變量,但是不會(huì)保存對(duì)象的方法,只會(huì)保存類的名字。 為了能夠unserialize()一個(gè)對(duì)象,這個(gè)對(duì)象的類必須已經(jīng)定義過。如果序列化類A的一個(gè)對(duì)象,將會(huì)返回一個(gè)跟類A相關(guān),而且包含了對(duì)象所有變量值的字符串。
簡(jiǎn)單說序列化是對(duì)象轉(zhuǎn)化字符串的過程,反序列化是字符串還原對(duì)象的過程。
環(huán)境
文章中所述內(nèi)容使用環(huán)境如下:
- PHP7.3.1、SDK
- VSCode
- C++和C
在網(wǎng)上公開參數(shù)反序列化執(zhí)行流程已經(jīng)非常詳細(xì),但是對(duì)于一些細(xì)節(jié)地方有一些不足,其中就包括序列化和反序列化之間的語法差異問題。
差異問題
1. 序列化
我們通過編譯PHP內(nèi)核源碼分析,發(fā)現(xiàn)PHP序列化在默認(rèn)情況下在對(duì)象轉(zhuǎn)換中加入:{和}用來拼接成字符串。
- [var.c]
- Line:882
- static void php_var_serialize_intern()
- Line:896
- if (ce->serialize(struc, &serialized_data, &serialized_length, (zend_serialize_data *)var_hash) == SUCCESS) {
- smart_str_appendl(buf, "C:", 2);
- smart_str_append_unsigned(buf, ZSTR_LEN(Z_OBJCE_P(struc)->name));
- smart_str_appendl(buf, ":\"", 2);
- smart_str_append(buf, Z_OBJCE_P(struc)->name);
- smart_str_appendl(buf, "\":", 2);
- smart_str_append_unsigned(buf, serialized_length);
- smart_str_appendl(buf, ":{", 2);
- smart_str_appendl(buf, (char *) serialized_data, serialized_length);
- smart_str_appendc(buf, '}');
- }
- Line:952
- smart_str_appendl(buf, ":{", 2);
- Line:995
- smart_str_appendc(buf, '}');
咱們來看上面這段代碼,PHP會(huì)使用smart_str_appendl為序列化字符串前后拼接:{和},從var.c的第882行開始進(jìn)入序列化邏輯。在第896行進(jìn)行序列化字符串拼接,第952行和第995行,對(duì)于內(nèi)嵌方法進(jìn)行拼接。
2. 反序列化
反序列化是將序列化的字符串,按照一定語法規(guī)則進(jìn)行轉(zhuǎn)化還原。
- [var_unserialize.c]
- Line:655
- static int php_var_unserialize_internal()
- Line:674
- {
- YYCTYPE yych;
- static const unsigned char yybm[] = {
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 128, 128, 128, 128, 128, 128, 128, 128,
- 128, 128, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- };
- if ((YYLIMIT - YYCURSOR) < 7) YYFILL(7);
- yych = *YYCURSOR;
- switch (yych) {
- case 'C':
- case 'O': goto yy4;
- case 'N': goto yy5;
- case 'R': goto yy6;
- case 'S': goto yy7;
- case 'a': goto yy8;
- case 'b': goto yy9;
- case 'd': goto yy10;
- case 'i': goto yy11;
- case 'o': goto yy12;
- case 'r': goto yy13;
- case 's': goto yy14;
- case '}': goto yy15;
- default: goto yy2;
- }
- Line:776
- yy15:
- ++YYCURSOR;
- {
- /* this is the case where we have less data than planned */
- php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data");
- return 0; /* not sure if it should be 0 or 1 here? */
- }
通過內(nèi)核代碼能夠看到第655行進(jìn)入反序列化,反序列化是利用詞法掃描,判斷各項(xiàng)符號(hào)轉(zhuǎn)換對(duì)應(yīng)對(duì)象。能夠看到反序列化中對(duì)于}進(jìn)行了處理,處理中只是對(duì)計(jì)數(shù)器加一并沒有其他操作。
實(shí)際作用
反序列化語法的差異,對(duì)于安全防護(hù)設(shè)備判斷反序列化產(chǎn)生很大的影響。在Snort中,有段規(guī)則如下:
- alert tcp any any -> any [80,8080,443] (uricontent:".php"; pcre:"/\{\w:.+?\}/"; sid:1; msg:php_serialize;)
在攻擊載荷中可以使用大多數(shù)字符代替{},從而導(dǎo)致規(guī)則失效。
總結(jié)
在紅隊(duì)攻擊中可以利用PHP序列化和反序列化語法差異,從而達(dá)到繞過防護(hù)的目的。
在藍(lán)隊(duì)防御中建議考慮定義中所述不會(huì)保存對(duì)象的方法,只會(huì)保存類的名字。,攔截保存類的名字,以及語法中相同的字符比如冒號(hào)進(jìn)行防御。