今天小編就為大家?guī)?lái)一篇PHP內(nèi)核層反序列化漏洞的文章。小編覺(jué)得挺不錯(cuò)的,為此分享給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。
前言
在學(xué)習(xí)PHP的過(guò)程中發(fā)現(xiàn)有些PHP特性的東西不好理解,如PHP中的00截?cái)啵琈D5缺陷,反序列化繞過(guò)__wakeup
等等。本人不想拘泥于表面現(xiàn)象的理解,想探究PHP內(nèi)核到底是怎樣做到的。
下面是將用CTF中常用的一個(gè)反序列化漏洞CVE-2016-7124(繞過(guò)魔法函數(shù)__wakeup)為例,將此次調(diào)試PHP內(nèi)核的過(guò)程分享出來(lái)。包括從內(nèi)核源碼調(diào)試環(huán)境的搭建,序列化與反序列化內(nèi)核源碼分析到最后的漏洞分析整個(gè)部分。
一、一個(gè)例子引發(fā)的思考
我們可以首先看本人寫(xiě)的小例子。
根據(jù)上圖我們先介紹下PHP中的魔法函數(shù):
我們先看下官方文檔對(duì)幾個(gè)常用魔法函數(shù)的介紹:
這里稍作總結(jié),當(dāng)一個(gè)類被初始化為實(shí)例時(shí)會(huì)調(diào)用__construct
,當(dāng)被銷毀時(shí)會(huì)調(diào)用__destruct
。
當(dāng)一個(gè)類調(diào)用serialize
進(jìn)行序列化時(shí)會(huì)自動(dòng)調(diào)用__sleep
函數(shù),當(dāng)字符串要利用unserialize
反序列化成一個(gè)類時(shí)會(huì)調(diào)用__wakeup
函數(shù)。上述魔法函數(shù)如果存在都將會(huì)自動(dòng)進(jìn)行調(diào)用。不用自己手動(dòng)進(jìn)行顯示調(diào)用。
現(xiàn)在我們來(lái)看最開(kāi)始的代碼部分,在__destruct
函數(shù)中有寫(xiě)入文件的敏感操作。我們這里利用反序列化構(gòu)造危險(xiǎn)的字符串有可能會(huì)造成代碼執(zhí)行漏洞。
當(dāng)我們構(gòu)造好相應(yīng)的字符串準(zhǔn)備進(jìn)行利用時(shí),我們卻發(fā)現(xiàn)它的__wakeup
函數(shù)中有過(guò)濾操作,這就給我們的構(gòu)造造成了阻礙。因?yàn)槲覀冎婪葱蛄谢療o(wú)論如何都是要先調(diào)用__wakeup
函數(shù)的。
這里我們不禁想到了利用這個(gè)PHP反序列化漏洞CVE-2016-7124(繞過(guò)魔法函數(shù)__wakeup),輕松繞過(guò)反序列化會(huì)自動(dòng)調(diào)用的魔法函數(shù)___wakeup,把敏感操作寫(xiě)入進(jìn)了文件。
當(dāng)然,上面的代碼只是我個(gè)人舉得一個(gè)簡(jiǎn)單例子,真實(shí)情況中不乏有上述類似情況的出現(xiàn)。但是這種繞過(guò)方法卻使我非常感興趣。PHP的內(nèi)部到底是如何操作和處理才會(huì)影響到上層代碼邏輯出現(xiàn)如此神奇的情況(BUG)。接下來(lái)本人將對(duì)PHP內(nèi)核進(jìn)行動(dòng)態(tài)調(diào)試分析。探究此問(wèn)題。
此漏洞(CVE-2016-7124)受影響版本PHP5系列為5.6.25之前,7.x系列為7.0.10之前。所以我們后面會(huì)編譯兩個(gè)版本:一為不受此漏洞影響的版本7.3.0,另一個(gè)版本為漏洞存在的版本5.6.10。通過(guò)兩個(gè)版本的對(duì)比來(lái)更詳細(xì)的了解其差異。
二、PHP源碼調(diào)試環(huán)境搭建
我們都知道PHP是由C語(yǔ)言開(kāi)發(fā),因本人所使用環(huán)境為WIN 10,所以主要介紹Windows下的環(huán)境搭建。我們需要如下材料:
PHP源碼 PHP SDK工具包,用于構(gòu)建PHP 調(diào)試所需要IDE
源碼可在GITHUB上下載,鏈接:https://github.com/php/php-src,可以選擇所需要的版本進(jìn)行下載。
PHPSDK 的工具包下載地址: https://github.com/Microsoft/php-sdk-binary-tools 這個(gè)地址所下載的工具包只支持VC14,VC15。當(dāng)然你也可以從https://windows.php.net/downloads/找到支持PHP低版本的VC11,VC12等,在使用PHP SDK之前必須保證你有安裝對(duì)應(yīng)版本W(wǎng)indows SDK組件的VS。
后文中會(huì)使用PHP7.3.0和5.6.10,下面會(huì)介紹這兩個(gè)版本的源碼編譯,其他版本手法類似。
2.1 編譯Windows PHP 7.3.0
本機(jī)環(huán)境WIN10 X64,PHP SDK是在上述github鏈接上下載。進(jìn)入SDK目錄,發(fā)現(xiàn)4個(gè)批處理文件,這里雙擊phpsdk-vc15-x64
。
接著在此shell中輸入 phpsdk_buildtreephp7
,會(huì)發(fā)現(xiàn)同目錄下出現(xiàn)了php7文件夾,并且shell目錄也發(fā)生了變化。
接著我們把解壓后的源碼放在\php7\vc15\x64下,shell進(jìn)入此文件夾內(nèi),利用phpsdk_deps–update–branchmaster
命令更新下載相關(guān)依賴組件。
等待完成后,進(jìn)入源碼目錄下雙擊buildconf.bat
批處理文件,它會(huì)釋放configure.bat
和configure.js
兩個(gè)文件,在shell中運(yùn)行configure–disable-all–enable-cli–enable-debug–enable-phar 配置相應(yīng)的編譯選項(xiàng),如還有別的需求,可執(zhí)行 configure –help 查看
根據(jù)提示,直接使用nmake進(jìn)行編譯。
編譯完成,可執(zhí)行文件目錄在php7\vc15\x64\php-src\x64\Debug_TS文件夾下。我們可輸入php -v查看相關(guān)信息。
2.2 編譯Windows PHP 5.6.10
方法跟7.3.0 相同,只需注意的是PHP5.6使用WindowsSDK 組件版本為VC11,需要下載VS2012,并且不能使用github上下載的PHP SDK進(jìn)行編譯,需要在 https://windows.php.net/downloads/ 上選擇VC11 的PHP SDK和相關(guān)依賴組件進(jìn)行編譯,其余和上述完全相同,這里不再重復(fù)。
2.3 調(diào)試配置
因?yàn)槲覀兩鲜鲆呀?jīng)編譯好了PHP解釋器,我們這里直接使用VSCODE來(lái)進(jìn)行調(diào)試。
下載完成后安裝C/C++調(diào)試擴(kuò)展。
接著打開(kāi)源碼目錄,點(diǎn)擊調(diào)試—>打開(kāi)配置,會(huì)打開(kāi)launch.json文件。
根據(jù)上圖,配置好這三個(gè)參數(shù)后,可在當(dāng)前目錄下1.php中寫(xiě)PHP代碼,在PHP源碼中下斷點(diǎn)直接進(jìn)行調(diào)試。
調(diào)試環(huán)境搭建完成。
三、PHP反序列化源碼解析
一般提及PHP反序列化,往往就是serialize和unserialize兩個(gè)成對(duì)出現(xiàn)的函數(shù),當(dāng)然必不可少的還有__sleep()和__wakeup()這兩個(gè)魔術(shù)方法。眾所周知,序列化簡(jiǎn)單點(diǎn)來(lái)說(shuō)就是對(duì)象存文件,反序列化剛好相反,從文件中把對(duì)象讀取出來(lái)并實(shí)例化。
下面,我們根據(jù)上面搭好的調(diào)試環(huán)境,通過(guò)動(dòng)態(tài)調(diào)試的手法來(lái)直觀的反應(yīng)PHP(7.3.0版本)中序列化與反序列化到底干了哪些事情。
3.1 serialize源碼分析
我們先寫(xiě)個(gè)不含有__sleep
魔法函數(shù)的簡(jiǎn)單Demo:
接著我們?cè)谠创a中全局搜索serialize
函數(shù),定位此函數(shù)是在var.c文件中。我們直接在函數(shù)頭下斷點(diǎn),并啟動(dòng)調(diào)試。
我們可見(jiàn)在做了一些準(zhǔn)備工作后,開(kāi)始進(jìn)入序列化處理函數(shù),我們跟進(jìn)php_var_serialize
函數(shù)。
我們這里繼續(xù)跟進(jìn)php_var_serialize_intern
函數(shù),下面就是主要處理函數(shù)了,因?yàn)楹瘮?shù)代碼比較多,我們這里只截出關(guān)鍵部分,此函數(shù)還在var.c文件中。
整個(gè)函數(shù)的結(jié)構(gòu)是switch case,通過(guò)宏Z_TYPE_P解析struc變體的類型(此宏展開(kāi)為struc->u1.v.type),來(lái)判斷要序列化的類型,從而進(jìn)入相應(yīng)的CASE部分進(jìn)行操作。下圖為類型定義。
根據(jù)上圖紅框中的數(shù)字8,我們可知此時(shí)需要要序列化為一個(gè)對(duì)象IS_OBJECT
,進(jìn)入相應(yīng)的CASE分支:
我們?cè)谏蠄D中看到了魔法函數(shù)__sleep
的調(diào)用時(shí)機(jī),因?yàn)槲覀儗?xiě)的Demo中并沒(méi)有此函數(shù),所以流程并不會(huì)進(jìn)入此分支。不同的分支代表不同的處理流程,我們稍后再看帶有魔法函數(shù)__sleep的流程。
因上面case IS_OBJECT分支中沒(méi)有流程命中,case中又沒(méi)有break語(yǔ)句,繼續(xù)執(zhí)行進(jìn)入IS_ARRAY分支,在這里從struc結(jié)構(gòu)中提取出類名,計(jì)算其長(zhǎng)度并賦值到buf結(jié)構(gòu)中,并提取出類中要序列化的結(jié)構(gòu)存入哈希數(shù)組中。
接下來(lái)就是利用php_var_serialize_intern
函數(shù)遞歸解析整個(gè)哈希數(shù)組的過(guò)程,從中分別提取出變量名和值進(jìn)行格式解析并將解析完成的字符串拼接到buf結(jié)構(gòu)中。最后當(dāng)整個(gè)過(guò)程結(jié)束后,整個(gè)字符串講完全存進(jìn)柔性數(shù)組結(jié)構(gòu)buf中。
從上圖紅框中可看出跟最終結(jié)果是相吻合的。我們接下來(lái)稍微修改下Demo,添加魔法函數(shù)__sleep
,根據(jù)官方文檔中描述,__sleep
函數(shù)必須返回一個(gè)數(shù)組。我們并在該函數(shù)中調(diào)用了一個(gè)類的成員函數(shù)。觀察其具體行為。
前面流程完全相同,此處不再重復(fù),我們從分支點(diǎn)開(kāi)始看。
我們直接跟進(jìn)php_var_serialize_call_sleep
函數(shù)。
我們這里繼續(xù)跟進(jìn)call_user_function
,根據(jù)宏定義,它實(shí)際上是調(diào)用了_call_user_function_ex
函數(shù),在這里做了一些拷貝動(dòng)作,故不做截圖,流程接下來(lái)進(jìn)入zend_call_function
函數(shù)的調(diào)用。
函數(shù)zend_call_function
中,實(shí)際情況下,在__sleep
中需要做一些我們自己的事情,這里PHP將要做的操作壓入PHP自己的zend_vm
引擎堆棧中,稍后會(huì)進(jìn)行一條條解析(就是解析相應(yīng)的OPCODE)。
這里流程會(huì)命中此分支,我們跟進(jìn)zend_execute_ex
函數(shù)。
我們這里可以看到在ZEND_VM中,整體體處理流程為while(1)循環(huán),不斷解析ZEND_VM棧中的操作。上圖紅框中ZEND_VM引擎會(huì)利用ZEND_FASTCALL
方式派發(fā)到到相應(yīng)的處理函數(shù)。
因?yàn)槲覀冊(cè)?code>__sleep中調(diào)用了成員函數(shù)show,這里首先定位出了show,接著會(huì)將接下來(lái)的操作繼續(xù)壓入ZEND_VM堆棧中進(jìn)行下一輪新的解析(這里是處理show中的操作),直到解析完整個(gè)操作為止。我們這里不再繼續(xù)跟進(jìn)。
還記得上面的傳出參數(shù)retval么,也就是__sleep的返回值,上圖為返回?cái)?shù)組的第一個(gè)元素x,當(dāng)然你也可以從變量中直接查看。
繞了這么大一圈,殊途同歸,在處理完_sleep函數(shù)中的一系列操作之后,接下來(lái)用php_var_serialize_class函數(shù)來(lái)序列化類名,遞歸序列化其_sleep函數(shù)返回值中的結(jié)構(gòu)。最終都把結(jié)果存在了buf結(jié)構(gòu)中。至此序列化的整個(gè)流程完畢。
3.1.1 serialize流程小結(jié)
我們總結(jié)下序列化的流程 :
當(dāng)沒(méi)有魔法函數(shù)時(shí),序列化類名–>利用遞歸序列化剩下的結(jié)構(gòu)
當(dāng)存在魔法函數(shù)時(shí),調(diào)用魔法函數(shù)__sleep–>利用ZEND_VM引擎解析PHP操作—>返回需要序列化結(jié)構(gòu)的數(shù)組–>序列化類名–>利用遞歸序列化__sleep的返回值結(jié)構(gòu)。
3.2 unserialize源碼分析
看完serialize的流程,接下來(lái),我們還是從最簡(jiǎn)單的一個(gè)Demo來(lái)看unserialize
流程。此例子不含魔法函數(shù)。
方法跟上面相同,unserialize
源碼也在var.c文件中。
上圖中涉及到了PHP7中的新特性,帶過(guò)濾的反序列化,根據(jù)allowed_classes
的設(shè)置情況來(lái)過(guò)濾相應(yīng)的PHP對(duì)象,防止非法數(shù)據(jù)注入。被過(guò)濾的對(duì)象會(huì)被轉(zhuǎn)化成__PHP_Incomplete_Class對(duì)
象不能被直接使用,但是這里對(duì)反序列化流程沒(méi)有影響,這里不做詳細(xì)探討。我們跟進(jìn)php_var_unserialize函數(shù)。
我們這里繼續(xù)跟入php_var_unserialize_internal
函數(shù)。
此函數(shù)內(nèi)部主要操作流程為對(duì)字符串進(jìn)行解析,然后跳轉(zhuǎn)到相應(yīng)的處理流程。上圖中解析出第一個(gè)字母0,代表此次反序列化為一個(gè)對(duì)象。
這里首先會(huì)解析出對(duì)象名字,并進(jìn)行查表操作確定此對(duì)象確實(shí)存在,我們繼續(xù)向下看。
上述操作做完之后,我們這里根據(jù)對(duì)象名稱new出了自己新的對(duì)象并進(jìn)行了初始化,但是我們的反序列化操作還是沒(méi)有完成,我們跟進(jìn)object_common2
函數(shù)。
在這里我們看到了對(duì)魔法函數(shù)的判斷與檢測(cè),但是調(diào)用部分并不在此。我們繼續(xù)跟進(jìn)process_nested_data函數(shù)。
看來(lái)這個(gè)函數(shù)利用WHILE循環(huán)來(lái)嵌套解析剩余的部分了,·其中包含兩個(gè)php_var_unserialize_internal函數(shù),第一個(gè)會(huì)解析名稱,第二個(gè)是解析名稱所對(duì)應(yīng)的值。process_nested_data函數(shù)運(yùn)行完畢后,字符串解析完畢,反序列化操作主要內(nèi)容已經(jīng)完成,流程即將進(jìn)入尾聲了。
逐層返回至最初的函數(shù)PHP_FUNCTION
中,我們看到就是一些掃尾工作了,釋放申請(qǐng)的空間,反序列化完畢。這里并沒(méi)有調(diào)用到我們的魔法函數(shù)__wakeup
。為了找出__wakeup
的調(diào)用時(shí)機(jī),我們這里修改下Demo。
這里開(kāi)始新的一輪調(diào)試。發(fā)現(xiàn)在序列化完成后,在PHP_VAR_UNSERIALIZE_DESTROY
釋放空間處出現(xiàn)了我們所希望看到的調(diào)用。
還記得反序列化流程中當(dāng)發(fā)現(xiàn)有__wakeup時(shí)對(duì)其進(jìn)行的VAR_WAKEUP_FLAG標(biāo)志么,在這里當(dāng)遍歷bar_dtor_hash數(shù)組遇到這個(gè)標(biāo)志時(shí),正式開(kāi)啟對(duì)__wakeup調(diào)用,后期的調(diào)用手法和前面所介紹的__sleep調(diào)用手法完全相同,這里不再做重復(fù)說(shuō)明。至此,反序列化所有流程完畢。
3.2.1 serialize流程小結(jié)
我們可以從上面可以看到,反序列化流程相對(duì)于序列化流程來(lái)說(shuō)并沒(méi)有因?yàn)槭欠癯霈F(xiàn)魔法函數(shù)來(lái)對(duì)流程造成分歧。Unserialize流程如下:
獲取反序列化字符串–>根據(jù)類型進(jìn)行反序列化—>查表找到對(duì)應(yīng)的反序列化類–>根據(jù)字符串判斷元素個(gè)數(shù)–>new出新實(shí)例–>迭代解析化剩下的字符串–>判斷是否具有魔法函數(shù)__wakeup并標(biāo)記—>釋放空間并判斷是否具有具有標(biāo)記—>開(kāi)啟調(diào)用。
四、PHP反序列化漏洞
有了上面源碼基礎(chǔ)的鋪墊,我們現(xiàn)在再來(lái)探究漏洞CVE-2016-7124(繞過(guò)__wakeup)魔法函數(shù)。
因此漏洞對(duì)版本有一定要求,我們使用上面編譯好的另一個(gè)PHP版本(5.6.10)來(lái)復(fù)現(xiàn)和調(diào)試此漏洞。
首先我們進(jìn)行一下漏洞復(fù)現(xiàn):
我們這里可以看到,TEST類中只包含一個(gè)元素$a,我們這里在反序列化時(shí)當(dāng)修改元素字符串中代表元素個(gè)數(shù)的數(shù)值時(shí),會(huì)觸發(fā)此漏洞,該類避過(guò)了魔法函數(shù)__wakeup
的調(diào)用。
當(dāng)然在觸發(fā)漏洞的過(guò)程中也發(fā)現(xiàn)了一個(gè)有趣的現(xiàn)象,觸發(fā)手段并不只有這一種。
上圖中4個(gè)payload所對(duì)應(yīng)的反序列化操作都會(huì)觸發(fā)此漏洞。雖然說(shuō)下方這四個(gè)都會(huì)觸發(fā)漏洞,但是其中還有一些微小的差別。這里我們稍微修改下代碼:
我們根據(jù)上圖可以看到,在反序列化的字符串中,只要在解析類中的元素出現(xiàn)錯(cuò)誤時(shí),都會(huì)觸發(fā)此漏洞。但是更改類元素內(nèi)部操作(如上圖的修改字符串長(zhǎng)度,類變量類型等)會(huì)導(dǎo)致類成員變量賦值失敗。只有修改類成員的個(gè)數(shù)(比原有成員個(gè)數(shù)大)時(shí),才能保證類成員賦值時(shí)成功的。
我們下面來(lái)通過(guò)調(diào)試來(lái)看問(wèn)題所在:
根據(jù)第三部分我們對(duì)反序列化源碼的分析,猜測(cè)可能是在最后解析變量那里出了問(wèn)題。我們這里直接上調(diào)試器動(dòng)態(tài)調(diào)試下:
我們可以看到,與7.3.0版本的源碼對(duì)比,此版本沒(méi)有過(guò)濾參數(shù),且經(jīng)過(guò)這么多版本的迭代,低版本的處理過(guò)程現(xiàn)在看來(lái)也相對(duì)簡(jiǎn)略。但是整體諧邏輯并沒(méi)有改變,我們這里直接跟進(jìn)php_var_unserialize函數(shù),此后相同邏輯不再進(jìn)行重復(fù)說(shuō)明,我們直接跟到差異處(object_common2函數(shù))也就是處理類中成員變量的代碼
在函數(shù)object_common2中,存在兩個(gè)主要操作,process_nested_data迭代解析類中的數(shù)據(jù)和魔法函數(shù)__wakeup的調(diào)用,且當(dāng)process_nested_data函數(shù)解析失敗后,直接返回0值,后面的__wakeup函數(shù)將沒(méi)有調(diào)用的機(jī)會(huì)。
這里就解釋了為何觸發(fā)漏洞不止一種payload。
當(dāng)只修改類成員的個(gè)數(shù)時(shí),while循環(huán)可以完成的進(jìn)行一次,這使得我們類中成員變量能被完整的賦值。當(dāng)修改成員變量?jī)?nèi)部時(shí),pap_var_unserialize函數(shù)調(diào)用失敗,緊接著會(huì)調(diào)用zval_dtor 和FREE_ZVAL函數(shù)釋放當(dāng)前key(變量)空間,導(dǎo)致類中的變量賦值失敗。
反觀在PHP7.3.0版本中此處并沒(méi)有出現(xiàn)調(diào)用過(guò)程,只是做了簡(jiǎn)單的標(biāo)記,整個(gè)魔法函數(shù)的調(diào)用過(guò)程的時(shí)機(jī)移至釋放數(shù)據(jù)處。這樣就避免了這個(gè)繞過(guò)的問(wèn)題。此漏洞應(yīng)該屬于邏輯上的缺陷導(dǎo)致的。
看完上述內(nèi)容,你們對(duì)PHP內(nèi)核層反序列化漏洞大概了解了嗎?如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計(jì)公司行業(yè)資訊頻道,感謝各位的閱讀!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。