溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PHP內(nèi)核層反序列化漏洞

發(fā)布時間:2020-05-12 15:48:24 來源:億速云 閱讀:250 作者:Leah 欄目:編程語言

今天小編就為大家?guī)硪黄狿HP內(nèi)核層反序列化漏洞的文章。小編覺得挺不錯的,為此分享給大家做個參考。一起跟隨小編過來看看吧。

前言

在學習PHP的過程中發(fā)現(xiàn)有些PHP特性的東西不好理解,如PHP中的00截斷,MD5缺陷,反序列化繞過__wakeup等等。本人不想拘泥于表面現(xiàn)象的理解,想探究PHP內(nèi)核到底是怎樣做到的。

下面是將用CTF中常用的一個反序列化漏洞CVE-2016-7124(繞過魔法函數(shù)__wakeup)為例,將此次調(diào)試PHP內(nèi)核的過程分享出來。包括從內(nèi)核源碼調(diào)試環(huán)境的搭建,序列化與反序列化內(nèi)核源碼分析到最后的漏洞分析整個部分。

一、一個例子引發(fā)的思考

我們可以首先看本人寫的小例子。

PHP內(nèi)核層反序列化漏洞

根據(jù)上圖我們先介紹下PHP中的魔法函數(shù):

我們先看下官方文檔對幾個常用魔法函數(shù)的介紹:

PHP內(nèi)核層反序列化漏洞

這里稍作總結(jié),當一個類被初始化為實例時會調(diào)用__construct,當被銷毀時會調(diào)用__destruct。

當一個類調(diào)用serialize進行序列化時會自動調(diào)用__sleep函數(shù),當字符串要利用unserialize反序列化成一個類時會調(diào)用__wakeup函數(shù)。上述魔法函數(shù)如果存在都將會自動進行調(diào)用。不用自己手動進行顯示調(diào)用。

現(xiàn)在我們來看最開始的代碼部分,在__destruct函數(shù)中有寫入文件的敏感操作。我們這里利用反序列化構(gòu)造危險的字符串有可能會造成代碼執(zhí)行漏洞。

當我們構(gòu)造好相應的字符串準備進行利用時,我們卻發(fā)現(xiàn)它的__wakeup函數(shù)中有過濾操作,這就給我們的構(gòu)造造成了阻礙。因為我們知道反序列化無論如何都是要先調(diào)用__wakeup函數(shù)的。

這里我們不禁想到了利用這個PHP反序列化漏洞CVE-2016-7124(繞過魔法函數(shù)__wakeup),輕松繞過反序列化會自動調(diào)用的魔法函數(shù)___wakeup,把敏感操作寫入進了文件。

當然,上面的代碼只是我個人舉得一個簡單例子,真實情況中不乏有上述類似情況的出現(xiàn)。但是這種繞過方法卻使我非常感興趣。PHP的內(nèi)部到底是如何操作和處理才會影響到上層代碼邏輯出現(xiàn)如此神奇的情況(BUG)。接下來本人將對PHP內(nèi)核進行動態(tài)調(diào)試分析。探究此問題。

此漏洞(CVE-2016-7124)受影響版本PHP5系列為5.6.25之前,7.x系列為7.0.10之前。所以我們后面會編譯兩個版本:一為不受此漏洞影響的版本7.3.0,另一個版本為漏洞存在的版本5.6.10。通過兩個版本的對比來更詳細的了解其差異。

二、PHP源碼調(diào)試環(huán)境搭建

我們都知道PHP是由C語言開發(fā),因本人所使用環(huán)境為WIN 10,所以主要介紹Windows下的環(huán)境搭建。我們需要如下材料:

PHP源碼
PHP SDK工具包,用于構(gòu)建PHP
調(diào)試所需要IDE

源碼可在GITHUB上下載,鏈接:https://github.com/php/php-src,可以選擇所需要的版本進行下載。

PHPSDK 的工具包下載地址: https://github.com/Microsoft/php-sdk-binary-tools 這個地址所下載的工具包只支持VC14,VC15。當然你也可以從https://windows.php.net/downloads/找到支持PHP低版本的VC11,VC12等,在使用PHP SDK之前必須保證你有安裝對應版本W(wǎng)indows SDK組件的VS。

后文中會使用PHP7.3.0和5.6.10,下面會介紹這兩個版本的源碼編譯,其他版本手法類似。

2.1 編譯Windows PHP 7.3.0

本機環(huán)境WIN10 X64,PHP SDK是在上述github鏈接上下載。進入SDK目錄,發(fā)現(xiàn)4個批處理文件,這里雙擊phpsdk-vc15-x64。

PHP內(nèi)核層反序列化漏洞

接著在此shell中輸入 phpsdk_buildtreephp7,會發(fā)現(xiàn)同目錄下出現(xiàn)了php7文件夾,并且shell目錄也發(fā)生了變化。

PHP內(nèi)核層反序列化漏洞

PHP內(nèi)核層反序列化漏洞

接著我們把解壓后的源碼放在\php7\vc15\x64下,shell進入此文件夾內(nèi),利用phpsdk_deps–update–branchmaster命令更新下載相關(guān)依賴組件。

等待完成后,進入源碼目錄下雙擊buildconf.bat批處理文件,它會釋放configure.batconfigure.js兩個文件,在shell中運行configure–disable-all–enable-cli–enable-debug–enable-phar 配置相應的編譯選項,如還有別的需求,可執(zhí)行 configure –help 查看

PHP內(nèi)核層反序列化漏洞

根據(jù)提示,直接使用nmake進行編譯。

PHP內(nèi)核層反序列化漏洞

編譯完成,可執(zhí)行文件目錄在php7\vc15\x64\php-src\x64\Debug_TS文件夾下。我們可輸入php -v查看相關(guān)信息。

PHP內(nèi)核層反序列化漏洞

2.2 編譯Windows PHP 5.6.10

方法跟7.3.0 相同,只需注意的是PHP5.6使用WindowsSDK 組件版本為VC11,需要下載VS2012,并且不能使用github上下載的PHP SDK進行編譯,需要在 https://windows.php.net/downloads/ 上選擇VC11 的PHP SDK和相關(guān)依賴組件進行編譯,其余和上述完全相同,這里不再重復。

PHP內(nèi)核層反序列化漏洞

2.3 調(diào)試配置

因為我們上述已經(jīng)編譯好了PHP解釋器,我們這里直接使用VSCODE來進行調(diào)試。

下載完成后安裝C/C++調(diào)試擴展。

PHP內(nèi)核層反序列化漏洞

接著打開源碼目錄,點擊調(diào)試—>打開配置,會打開launch.json文件。

PHP內(nèi)核層反序列化漏洞

根據(jù)上圖,配置好這三個參數(shù)后,可在當前目錄下1.php中寫PHP代碼,在PHP源碼中下斷點直接進行調(diào)試。

調(diào)試環(huán)境搭建完成。

三、PHP反序列化源碼解析

一般提及PHP反序列化,往往就是serialize和unserialize兩個成對出現(xiàn)的函數(shù),當然必不可少的還有__sleep()和__wakeup()這兩個魔術(shù)方法。眾所周知,序列化簡單點來說就是對象存文件,反序列化剛好相反,從文件中把對象讀取出來并實例化。

下面,我們根據(jù)上面搭好的調(diào)試環(huán)境,通過動態(tài)調(diào)試的手法來直觀的反應PHP(7.3.0版本)中序列化與反序列化到底干了哪些事情。

3.1 serialize源碼分析

我們先寫個不含有__sleep魔法函數(shù)的簡單Demo:

PHP內(nèi)核層反序列化漏洞

接著我們在源碼中全局搜索serialize函數(shù),定位此函數(shù)是在var.c文件中。我們直接在函數(shù)頭下斷點,并啟動調(diào)試。

PHP內(nèi)核層反序列化漏洞

我們可見在做了一些準備工作后,開始進入序列化處理函數(shù),我們跟進php_var_serialize函數(shù)。

PHP內(nèi)核層反序列化漏洞

我們這里繼續(xù)跟進php_var_serialize_intern函數(shù),下面就是主要處理函數(shù)了,因為函數(shù)代碼比較多,我們這里只截出關(guān)鍵部分,此函數(shù)還在var.c文件中。

PHP內(nèi)核層反序列化漏洞

整個函數(shù)的結(jié)構(gòu)是switch case,通過宏Z_TYPE_P解析struc變體的類型(此宏展開為struc->u1.v.type),來判斷要序列化的類型,從而進入相應的CASE部分進行操作。下圖為類型定義。

PHP內(nèi)核層反序列化漏洞

根據(jù)上圖紅框中的數(shù)字8,我們可知此時需要要序列化為一個對象IS_OBJECT,進入相應的CASE分支:

PHP內(nèi)核層反序列化漏洞

我們在上圖中看到了魔法函數(shù)__sleep的調(diào)用時機,因為我們寫的Demo中并沒有此函數(shù),所以流程并不會進入此分支。不同的分支代表不同的處理流程,我們稍后再看帶有魔法函數(shù)__sleep的流程。

PHP內(nèi)核層反序列化漏洞

因上面case IS_OBJECT分支中沒有流程命中,case中又沒有break語句,繼續(xù)執(zhí)行進入IS_ARRAY分支,在這里從struc結(jié)構(gòu)中提取出類名,計算其長度并賦值到buf結(jié)構(gòu)中,并提取出類中要序列化的結(jié)構(gòu)存入哈希數(shù)組中。

PHP內(nèi)核層反序列化漏洞

接下來就是利用php_var_serialize_intern函數(shù)遞歸解析整個哈希數(shù)組的過程,從中分別提取出變量名和值進行格式解析并將解析完成的字符串拼接到buf結(jié)構(gòu)中。最后當整個過程結(jié)束后,整個字符串講完全存進柔性數(shù)組結(jié)構(gòu)buf中。

PHP內(nèi)核層反序列化漏洞

從上圖紅框中可看出跟最終結(jié)果是相吻合的。我們接下來稍微修改下Demo,添加魔法函數(shù)__sleep,根據(jù)官方文檔中描述,__sleep函數(shù)必須返回一個數(shù)組。我們并在該函數(shù)中調(diào)用了一個類的成員函數(shù)。觀察其具體行為。

PHP內(nèi)核層反序列化漏洞

前面流程完全相同,此處不再重復,我們從分支點開始看。

PHP內(nèi)核層反序列化漏洞

我們直接跟進php_var_serialize_call_sleep函數(shù)。

PHP內(nèi)核層反序列化漏洞

我們這里繼續(xù)跟進call_user_function,根據(jù)宏定義,它實際上是調(diào)用了_call_user_function_ex函數(shù),在這里做了一些拷貝動作,故不做截圖,流程接下來進入zend_call_function函數(shù)的調(diào)用。

PHP內(nèi)核層反序列化漏洞

函數(shù)zend_call_function中,實際情況下,在__sleep中需要做一些我們自己的事情,這里PHP將要做的操作壓入PHP自己的zend_vm引擎堆棧中,稍后會進行一條條解析(就是解析相應的OPCODE)。

PHP內(nèi)核層反序列化漏洞

這里流程會命中此分支,我們跟進zend_execute_ex函數(shù)。

PHP內(nèi)核層反序列化漏洞

我們這里可以看到在ZEND_VM中,整體體處理流程為while(1)循環(huán),不斷解析ZEND_VM棧中的操作。上圖紅框中ZEND_VM引擎會利用ZEND_FASTCALL方式派發(fā)到到相應的處理函數(shù)。

PHP內(nèi)核層反序列化漏洞

PHP內(nèi)核層反序列化漏洞

因為我們在__sleep中調(diào)用了成員函數(shù)show,這里首先定位出了show,接著會將接下來的操作繼續(xù)壓入ZEND_VM堆棧中進行下一輪新的解析(這里是處理show中的操作),直到解析完整個操作為止。我們這里不再繼續(xù)跟進。

PHP內(nèi)核層反序列化漏洞

還記得上面的傳出參數(shù)retval么,也就是__sleep的返回值,上圖為返回數(shù)組的第一個元素x,當然你也可以從變量中直接查看。

繞了這么大一圈,殊途同歸,在處理完_sleep函數(shù)中的一系列操作之后,接下來用php_var_serialize_class函數(shù)來序列化類名,遞歸序列化其_sleep函數(shù)返回值中的結(jié)構(gòu)。最終都把結(jié)果存在了buf結(jié)構(gòu)中。至此序列化的整個流程完畢。

3.1.1 serialize流程小結(jié)

我們總結(jié)下序列化的流程 :

當沒有魔法函數(shù)時,序列化類名–>利用遞歸序列化剩下的結(jié)構(gòu)

當存在魔法函數(shù)時,調(diào)用魔法函數(shù)__sleep–>利用ZEND_VM引擎解析PHP操作—>返回需要序列化結(jié)構(gòu)的數(shù)組–>序列化類名–>利用遞歸序列化__sleep的返回值結(jié)構(gòu)。

3.2 unserialize源碼分析

看完serialize的流程,接下來,我們還是從最簡單的一個Demo來看unserialize流程。此例子不含魔法函數(shù)。

PHP內(nèi)核層反序列化漏洞

方法跟上面相同,unserialize源碼也在var.c文件中。

PHP內(nèi)核層反序列化漏洞

PHP內(nèi)核層反序列化漏洞

上圖中涉及到了PHP7中的新特性,帶過濾的反序列化,根據(jù)allowed_classes的設置情況來過濾相應的PHP對象,防止非法數(shù)據(jù)注入。被過濾的對象會被轉(zhuǎn)化成__PHP_Incomplete_Class對象不能被直接使用,但是這里對反序列化流程沒有影響,這里不做詳細探討。我們跟進php_var_unserialize函數(shù)。

PHP內(nèi)核層反序列化漏洞

我們這里繼續(xù)跟入php_var_unserialize_internal函數(shù)。

PHP內(nèi)核層反序列化漏洞

此函數(shù)內(nèi)部主要操作流程為對字符串進行解析,然后跳轉(zhuǎn)到相應的處理流程。上圖中解析出第一個字母0,代表此次反序列化為一個對象。

PHP內(nèi)核層反序列化漏洞

這里首先會解析出對象名字,并進行查表操作確定此對象確實存在,我們繼續(xù)向下看。

PHP內(nèi)核層反序列化漏洞

上述操作做完之后,我們這里根據(jù)對象名稱new出了自己新的對象并進行了初始化,但是我們的反序列化操作還是沒有完成,我們跟進object_common2函數(shù)。

在這里我們看到了對魔法函數(shù)的判斷與檢測,但是調(diào)用部分并不在此。我們繼續(xù)跟進process_nested_data函數(shù)。

PHP內(nèi)核層反序列化漏洞

PHP內(nèi)核層反序列化漏洞

看來這個函數(shù)利用WHILE循環(huán)來嵌套解析剩余的部分了,·其中包含兩個php_var_unserialize_internal函數(shù),第一個會解析名稱,第二個是解析名稱所對應的值。process_nested_data函數(shù)運行完畢后,字符串解析完畢,反序列化操作主要內(nèi)容已經(jīng)完成,流程即將進入尾聲了。

PHP內(nèi)核層反序列化漏洞

逐層返回至最初的函數(shù)PHP_FUNCTION中,我們看到就是一些掃尾工作了,釋放申請的空間,反序列化完畢。這里并沒有調(diào)用到我們的魔法函數(shù)__wakeup。為了找出__wakeup的調(diào)用時機,我們這里修改下Demo。

PHP內(nèi)核層反序列化漏洞

這里開始新的一輪調(diào)試。發(fā)現(xiàn)在序列化完成后,在PHP_VAR_UNSERIALIZE_DESTROY釋放空間處出現(xiàn)了我們所希望看到的調(diào)用。

PHP內(nèi)核層反序列化漏洞

還記得反序列化流程中當發(fā)現(xiàn)有__wakeup時對其進行的VAR_WAKEUP_FLAG標志么,在這里當遍歷bar_dtor_hash數(shù)組遇到這個標志時,正式開啟對__wakeup調(diào)用,后期的調(diào)用手法和前面所介紹的__sleep調(diào)用手法完全相同,這里不再做重復說明。至此,反序列化所有流程完畢。

3.2.1 serialize流程小結(jié)

我們可以從上面可以看到,反序列化流程相對于序列化流程來說并沒有因為是否出現(xiàn)魔法函數(shù)來對流程造成分歧。Unserialize流程如下:

獲取反序列化字符串–>根據(jù)類型進行反序列化—>查表找到對應的反序列化類–>根據(jù)字符串判斷元素個數(shù)–>new出新實例–>迭代解析化剩下的字符串–>判斷是否具有魔法函數(shù)__wakeup并標記—>釋放空間并判斷是否具有具有標記—>開啟調(diào)用。

四、PHP反序列化漏洞

有了上面源碼基礎的鋪墊,我們現(xiàn)在再來探究漏洞CVE-2016-7124(繞過__wakeup)魔法函數(shù)。

因此漏洞對版本有一定要求,我們使用上面編譯好的另一個PHP版本(5.6.10)來復現(xiàn)和調(diào)試此漏洞。

首先我們進行一下漏洞復現(xiàn):

PHP內(nèi)核層反序列化漏洞

我們這里可以看到,TEST類中只包含一個元素$a,我們這里在反序列化時當修改元素字符串中代表元素個數(shù)的數(shù)值時,會觸發(fā)此漏洞,該類避過了魔法函數(shù)__wakeup的調(diào)用。

當然在觸發(fā)漏洞的過程中也發(fā)現(xiàn)了一個有趣的現(xiàn)象,觸發(fā)手段并不只有這一種。

PHP內(nèi)核層反序列化漏洞

上圖中4個payload所對應的反序列化操作都會觸發(fā)此漏洞。雖然說下方這四個都會觸發(fā)漏洞,但是其中還有一些微小的差別。這里我們稍微修改下代碼:

PHP內(nèi)核層反序列化漏洞

我們根據(jù)上圖可以看到,在反序列化的字符串中,只要在解析類中的元素出現(xiàn)錯誤時,都會觸發(fā)此漏洞。但是更改類元素內(nèi)部操作(如上圖的修改字符串長度,類變量類型等)會導致類成員變量賦值失敗。只有修改類成員的個數(shù)(比原有成員個數(shù)大)時,才能保證類成員賦值時成功的。

我們下面來通過調(diào)試來看問題所在:

根據(jù)第三部分我們對反序列化源碼的分析,猜測可能是在最后解析變量那里出了問題。我們這里直接上調(diào)試器動態(tài)調(diào)試下:

PHP內(nèi)核層反序列化漏洞

我們可以看到,與7.3.0版本的源碼對比,此版本沒有過濾參數(shù),且經(jīng)過這么多版本的迭代,低版本的處理過程現(xiàn)在看來也相對簡略。但是整體諧邏輯并沒有改變,我們這里直接跟進php_var_unserialize函數(shù),此后相同邏輯不再進行重復說明,我們直接跟到差異處(object_common2函數(shù))也就是處理類中成員變量的代碼

PHP內(nèi)核層反序列化漏洞

在函數(shù)object_common2中,存在兩個主要操作,process_nested_data迭代解析類中的數(shù)據(jù)和魔法函數(shù)__wakeup的調(diào)用,且當process_nested_data函數(shù)解析失敗后,直接返回0值,后面的__wakeup函數(shù)將沒有調(diào)用的機會。

這里就解釋了為何觸發(fā)漏洞不止一種payload。

PHP內(nèi)核層反序列化漏洞

當只修改類成員的個數(shù)時,while循環(huán)可以完成的進行一次,這使得我們類中成員變量能被完整的賦值。當修改成員變量內(nèi)部時,pap_var_unserialize函數(shù)調(diào)用失敗,緊接著會調(diào)用zval_dtor 和FREE_ZVAL函數(shù)釋放當前key(變量)空間,導致類中的變量賦值失敗。

反觀在PHP7.3.0版本中此處并沒有出現(xiàn)調(diào)用過程,只是做了簡單的標記,整個魔法函數(shù)的調(diào)用過程的時機移至釋放數(shù)據(jù)處。這樣就避免了這個繞過的問題。此漏洞應該屬于邏輯上的缺陷導致的。

看完上述內(nèi)容,你們對PHP內(nèi)核層反序列化漏洞大概了解了嗎?如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI