[0CTF 2016]piapiapia


很有意思的一道題

訪問頁面之后是登錄界面

嘗試弱口令登錄一下,無果

查看源代碼之后也沒有什么提示,掃描敏感目錄,發現有源碼泄露。

這里我用御劍沒有掃出來源碼泄露,可能跟掃描線程太快了有關,查看www.zip里面泄露的源代碼

class.php里面定義了user和mysql兩個類

config.php里面是服務器搭建環境的時候設置的參數,如果讀者有自己本地搭建環境的經驗就會知道,我們下載下來源碼之后,還需要根據自己本地的環境進行相應的配置,比如說我們需要在config.php里面設置自己本地數據庫的用戶名和密碼,這些在下載下來的config.php的源代碼里面都是暫時空缺的。

所以題目環境docker下發的時候,一定也設置了自己本地的$flag的值,於是我們的目標就是需要讀取服務器端config.php文件,就能夠得到flag了。

在register.php里面,可以看到是注冊一個用戶,輸入用戶名和密碼,接着跳轉到index.php界面進行登錄

在index.php里面我們輸入用戶名和密碼進行登錄,接着跳轉到profile.php頁面。

在此之前我們需要傳遞$profile的值

這里對我們輸入的phone,email,nickname都進行了過濾,在源碼的注釋里面我已經進行了說明,很明顯最后一個if語句的判斷跟前兩者有所不同,前兩者如果phone為數組的話,匹配失敗則為null,取反后就die,而第三個if當nickname為數組的時候,它不會匹配到非數字字母的值,也就為false,長度處使用數組strlen函數也會失效,返回NULL,則繞過了此處的if過濾。

這里添加一個知識點,正則表達式[]內的^表示匹配不存在這類字符的,如圖上的第三個if里面的,表示匹配非數字字母的值,在[]外的^表示匹配字符串開頭。

所以對於輸入的nickname的值我們是可控的。

再去看profile.php

可以看到這里對$profile的值進行了反序列化,接着依次讀取,此時對於$photo有一個file_get_contents()文件讀取函數,所以這里是我們破題的關鍵。

值得一提的是,在class.php的mysql類的定義中,過濾函數為

可以看出來將黑名單數組里面的值都換成了hacker

現在我們整理一下思路,update.php中有一個$profile數組變量,這個數組里有$phone, $email, $nickname, $photo幾個變量,序列化后profile字段存入數據庫,當我們訪問profile.php的時候,則會從數據庫里面讀取profile字段同時反序列化,我們需要控制$photo為config.php,才可以在訪問的時候獲取到base64編碼之后的有flag值的服務器端的config.php

而我們唯一能夠控制的變量則是之前說到的nickname,參考了很多師傅的WP之后,寫一下這道題的主要知識點:

PHP序列化長度變化導致字符逃逸

首先, PHP反序列化中值的字符讀取多少其實是由表示長度的數字控制的,而且只要整個字符串的前一部分能夠成功反序列化,這個字符串后面剩下的一部分將會被丟棄

簡單舉幾個例子大家就明白了


得到這個結果很正常

接着我們改變一點點

得到的結果變成了hello woxxx

我們可以看到,原來的字符串hello world內被填充了幾個字符串,即xxx”;},在PHP進行反序列化時,由字符串初始位置向后讀取8個字符,即使遇到字符串分解符單雙引號也會繼續向下讀,此處讀取到 woxxx ,而后遇到了正常的結束符”;},達成了正常反序列化的條件,反序列化結束,后面的 rld”;}  幾個字符均被丟棄。

我們用另外一個例子來學習一下這個知識點的應用

可以看到bad_str函數會將序列化之后的單引號轉換成為字符串no,實際上這里就已經有了長度的變化,因為單引號長度是1,而no字符串長度是2

接着,我們修改用戶的簽名

這里偷一張別的師傅的圖,雖然輸入的值不同,但是思想是相同的

在我們這里,則是想將hello world改變掉。

於是構造

輸出的結果跟我們想要的是相同的,hello world變成了hhh

參考一下大佬博客里面對此的解釋

在反序列化輸出之前,我們的字符串是在某過濾函數的過濾替換之后得到的,在經過過濾處理了之后,字符串的某一部分會加長,但是描述其長度的數字沒有發生改變(由反序列化時變量的屬性決定),這就可能導致PHP在按描述其長度的數字讀取相應長度的字符串之后,本該屬於該字符串的內容逃逸出了該字符串的管轄范圍。輕則反序列化失敗,重則自成一家成為一個獨立於源字符串的變量,若是這個獨立出來的變量末尾是個結束符";},則可能導致反序列化成功結束,而后面的內容也順理成章的被丟棄了,此處能夠逃逸出的字符串長度由過濾后字符串增加的長度決定,如上圖第四個語句,@號內就是我們要逃逸出來的字符串,長度為33,百分號內為我們輸入的username變量,要想讓@號內的字符串逃逸,我們就需要原來的字符串增加33,這樣的話@號內的字符串就會被擠出它原來所在的位置,username的正常部分和增長的部分正好被php解析成一整個變量,@號內的內容就被解析成一個獨立的變量,而且因為它的最后有";},所以使反序列化成功結束。

再回到我在本地的例子上面,我們為了使hhh替換掉hello world,於是這一段是需要逃逸出來的字符串

";i:1;s:3:"hhh";}

長度為17,而單引號替換成no之后長度只是增加1,所以為了增加17個長度,我們需要17個單引號,這樣才能夠將逃逸字符串擠出原來的位置。

緊接着,17*no+test,一共是38個字符,所以在s處我們填寫長度為38

最后的$fakes就為

a:2:{i:0;s:38:"test'''''''''''''''''";i:1;s:3:"hhh";};i:1;s:11:"hello world";}

所以在此處我們最終字符串替換成為了hhh

先閉合了一個變量的正確格式,又寫入了一個變量的正確格式,最后閉合了一個反序列化的操作。該擠出的被擠出逃逸了,該丟棄的丟棄了,最后想要達成的目標也實現了。

於是我們再回歸到題目里面來,因為我們最后是從數據庫里面讀取反序列化之后的結果,所以我們先在本地搭建序列化,看看序列化的格式之后編寫相應的payload

可以看到,根據泄露的源碼序列化之后的結果為

a:4:{s:5:"phone";s:6:"123456";s:5:"email";s:12:"test@126.com";s:8:"nickname";a:1:{i:0;s:4:"hell";}s:5:"photo";s:5:"hello";}

我們能夠構造的是nickname,在這里我已經是傳遞數組給他了

我們需要將photo的值hello改變成config.php

根據之前的基礎知識,本地的直接構造為:

a:4:{s:5:"phone";s:6:"123456";s:5:"email";s:12:"test@126.com";s:8:"nickname";a:1:{i:0;s:72:"hell''''''''''''''''''''''''''''''''''";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:5:"hello";}

需要逃逸的字符串為

";}s:5:"photo";s:10:"config.php";}

長度為34,所以添加34個單引號,長度為34*2+4即72

可以看到本地的查看的文件已經修改為了config.php

我們在題目里面使用相同的思想

因為where轉換成hacker會由5–>6,字符有一個增加,所以我們為了逃逸34個字符,就添加34*where

警告無傷大雅,因為我們傳遞的是數組類型的值,所以這里會有警告

源代碼里面的base64編碼就是config.php的base64編碼,解碼即可

可以看到成功讀取了config.php,里面有flag的值

 

貼上參考的師傅的博客鏈接

 https://www.jianshu.com/p/3b44e72444c1 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM