您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關怎樣進行由Typecho深入理解PHP反序列化漏洞,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
Typecho是一個輕量版的博客系統,前段時間爆出getshell漏洞,網上也已經有相關的漏洞分析發布。這個漏洞是由PHP反序列化漏洞造成的,所以這里我們分析一下這個漏洞,并借此漏洞深入理解PHP反序列化漏洞。
1.1 漏洞簡介
PHP反序列化漏洞也叫PHP對象注入,是一個非常常見的漏洞,這種類型的漏洞雖然有些難以利用,但一旦利用成功就會造成非常危險的后果。漏洞的形成的根本原因是程序沒有對用戶輸入的反序列化字符串進行檢測,導致反序列化過程可以被惡意控制,進而造成代碼執行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等語言之中,但其原理基本相通。
1.2 漏洞原理
接下來我們通過幾個實例來理解什么是PHP序列化與反序列化以及漏洞形成的具體過程,首先建立1.php文件文件內容如下:
文件中有一個TestClass類,類中定義了一個$variable變量和一個PrintVariable函數,然后實例化這個類并調用它的方法,運行結果如下:
這是一個正常的類的實例化和成員函數調用過程,但是有一些特殊的類成員函數在某些特定情況下會自動調用,稱之為magic函數,magic函數命名是以符號__開頭的,比如__construct當一個對象創建時被調用,__destruct當一個對象銷毀時被調用,__toString當一個對象被當作一個字符串被調用。為了更好的理解magic方法是如何工作的,在2.php中增加了三個magic方法,__construct, __destruct和__toString。
運行結果如下,注意還有其他的magic方法,這里只列舉了幾個。
php允許保存一個對象方便以后重用,這個過程被稱為序列化。為什么要有序列化這種機制呢?因為在傳遞變量的過程中,有可能遇到變量值要跨腳本文件傳遞的過程。試想,如果在一個腳本中想要調用之前一個腳本的變量,但是前一個腳本已經執行完畢,所有的變量和內容釋放掉了,我們要如何操作呢?難道要前一個腳本不斷的循環,等待后面腳本調用?這肯定是不現實的。serialize和unserialize就是用來解決這一問題的。serialize可以將變量轉換為字符串并且在轉換中可以保存當前變量的值;unserialize則可以將serialize生成的字符串變換回變量。讓我們在3.php中添加序列化的例子,看看php對象序列化之后的格式。
輸出如下
O表示對象,4表示對象名長度為4,”User”為類名,2表示成員變量個數,大括號里分別為變量的類型、名稱、長度及其值。想要將這個字符串恢復成類對象需要使用unserialize重建對象,在4.php中寫入如下代碼
運行結果
magic函數__construct和__destruct會在對象創建或者銷毀時自動調用,__sleep方法在一個對象被序列化的時候調用,__wakeup方法在一個對象被反序列化的時候調用。在5.php中添加這幾個magic函數的例子。
運行結果
OK,到此我們已經知道了magic函數、序列化與反序列化這幾個重要概念,那么這個過程漏洞是怎么產生的呢?我們再來看一個例子6.php
這段代碼包含兩個類,一個example和一個process,在process中有一個成員函數close(),其中有一個eval()函數,但是其參數不可控,我們無法利用它執行任意代碼。但是在example類中有一個__destruct()析構函數,它會在腳本調用結束的時候執行,析構函數調用了本類中的一個成員函數shutdown(),其作用是調用某個地方的close()函數。于是開始思考這樣一個問題:能否讓他去調用process中的close()函數且$pid變量可控呢?答案是可以的,只要在反序列化的時候$handle是process的一個類對象,$pid是想要執行的任意代碼代碼即可,看一下如何構造POC
執行效果
當我們序列化的字符串進行反序列化時就會按照我們的設定生成一個example類對象,當腳本結束時自動調用__destruct()函數,然后調用shutdown()函數,此時$handle為process的類對象,所以接下來會調用process的close()函數,eval()就會執行,而$pid也可以進行設置,此時就造成了代碼執行。這整個攻擊線路我們稱之為ROP(Return-oriented programming)鏈,其核心思想是在整個進程空間內現存的函數中尋找適合代碼片斷(gadget),并通過精心設計返回代碼把各個gadget拼接起來,從而達到惡意攻擊的目的。構造ROP攻擊的難點在于,我們需要在整個進程空間中搜索我們需要的gadgets,這需要花費相當長的時間。但一旦完成了“搜索”和“拼接”,這樣的攻擊是無法抵擋的,因為它用到的都是程序中合法的的代碼,普通的防護手段難以檢測。反序列化漏洞需要滿足兩個條件:
1、程序中存在序列化字符串的輸入點 2、程序中存在可以利用的magic函數
接下來通過Typecho的序列化漏洞進行實戰分析。
漏洞的位置發生在install.php,首先有一個referer的檢測,使其值為一個站內的地址即可繞過。
入口點在232行
這里將cookie中的__typecho_config值取出,然后base64解碼再進行反序列化,這就滿足了漏洞發生的第一個條件:存在序列化字符串的輸入點。接下來就是去找一下有什么magic方法可以利用。先全局搜索__destruct()和__wakeup()
找到兩處__destruct(),跟進去沒有可利用的地方,跟著代碼往下走會實例化一個Typecho_Db,位于var\Typecho\Db.php,Typecho_Db的構造函數如下
在第120行使用.運算符連接$adapterName,這時$adapterName如果是一個實例化的對象就會自動調用__toString方法(如果存在的話),那全局搜索一下__toString()方法。找到3處
前兩處無法利用,跟進第三處,__toString()在var\Typecho\Feed.php 223行
跟進代碼在290處有如下代碼
如何$item['author']是一個類而screenName是一個無法被直接調用的變量(私有變量或根本就不存在的變量),則會自動調用__get() magic方法,進而再去尋找可以利用的__get()方法,全局搜索
共匹配到10處,其中在var\Typecho\Request.php中的代碼可以利用,跟進
再跟進到get函數
接著進入_applyFilter函數
可以看到array_map和call_user_func函數,他們都可以動態的執行函數,第一個參數表示要執行的函數的名稱,第二個參數表示要執行的函數的參數。我們可以在這里嘗試執行任意代碼。接下來梳理一下整個流程,數據的輸入點在install.php文件的232行,從外部讀入序列化的數據。然后根據我們構造的數據,程序會進入Db.php的__construct()函數,然后進入Feed.php的__toString()函數,再依次進入Request.php的__get()、get()、_applyFilter()函數,最后由call_user_func實現任意代碼執行,整個ROP鏈形成。構造POC如下
POC的22行其實與反序列化無關,但是不加這一行程序就不會有回顯,因為在 install.php 的開頭部分調用了程序調用了ob_start(),它會開啟緩沖區并將要輸出的內容都放進緩沖區,想要使用的時候可以再取出。但是我們的對象注入會在后續的代碼中造成數據庫錯誤
然后會觸發exception,其中的ob_end_clean()會將緩沖區中的內容清空,導致無法回顯。
想要解決這個問題需要在ob_end_clean()執行之前是程序退出,兩種方法:
1、使程序跳轉到存在exit()的代碼段
2、使程序提前報錯,退出代碼
POC中使用的是第二種方法
解決了上述問題后就可以執行任意代碼并能看到回顯了,執行的時候在http頭添加referre使其等于一個站內地址,然后在cookie中添加字段__typecho_config,其值為上述exp的輸出。
有些利用方式并不需要回顯,比如寫個shell什么的,POC如下
執行結果,在根目錄生成shell.php
上述就是小編為大家分享的怎樣進行由Typecho深入理解PHP反序列化漏洞了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。