今日重拾web昔善,在BUUCTF上選個簡單的題目[MRCTF2020]Ezpop做一下宠进,
題目非常直接屏箍,
?整個思路還是比較好想的称勋,在找思路之前我們需要先明確先驗知識胸哥,
1.
參考鏈接
https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
__construct()//當一個對象創(chuàng)建時被調用
__destruct() //當一個對象銷毀時被調用
__toString() //當一個對象被當作一個字符串使用
__sleep()//在對象在被序列化之前運行
__wakeup()//將在反序列化之后立即被調用(通過序列化對象元素個數(shù)不符來繞過)
__get()//獲得一個類的成員變量時調用
__set()//設置一個類的成員變量時調用
__invoke()//調用函數(shù)的方式調用一個對象時的回應方法
__call()//當調用一個對象中的不能用的方法的時候就會執(zhí)行這個函數(shù)
此處用到的主要是__toString(),__wakeup()赡鲜,__get()空厌,__invoke(),
2.
參考鏈接
https://blog.csdn.net/nzjdsds/article/details/104011576
類中银酬,private變量與protected變量序列化后嘲更,變量名會有些異常,
private變量經(jīng)反序列化后為\x00 + 類名 + \x00 + 變量名揩瞪;
protected變量經(jīng)反序列化后為赋朦,\x00 + * + \x00 + 變量名;
3.
參考鏈接
忘記是自己總結的還是從哪里抄的了
類成員由屬性和方法構成李破,類屬性存在于數(shù)據(jù)段宠哄,類方法存在于代碼段,對于一個類來說嗤攻,類的方法不占用類的空間毛嫉,占空間的只有類的屬性。序列化一個對象將會保存對象的所有變量妇菱,但是不會保存對象的方法承粤,只會保存類的名字。因此闯团,序列化操作只是保存對象(不是類)的變量辛臊,不保存對象的方法,其實反序列化的主要危害在于我們可以控制對象的變量來改變程序執(zhí)行流程從而達到我們最終的目的房交。我們無法控制對象的方法來調用彻舰,因此我們這里只能去找一些可以自動調用的一些魔術方法。
舉例如下涌萤,
捋一下思路淹遵,題目中說了flag在flag.php里。
我們首先注意到Modifier類的append()方法中有include负溪,結合題意,此處應該是用來包含flag.php的济炎,當然此處我們需要使用php://filter來讀取編碼后的川抡,否則直接include相當于執(zhí)行而已,看不到結果,
可以想象崖堤,Modifier類是觸發(fā)漏洞的最后一環(huán)侍咱,我們再看看,可以看到其還有一個魔術方法__invoke()密幔,可以在作為函數(shù)被調用時觸發(fā)楔脯,
正常情況下,我們只能進行這么一步反序列化操作胯甩,應該是無法直接調用Modifier的__invoke()的昧廷,
這就要求我們去找能調用__invoke()的地方,
順著這個線索偎箫,我們找到Test類的__get()魔術方法木柬,
這里Test類的__construct函數(shù)是假的,不用管淹办,關注__get()函數(shù) 眉枕,其中直接將$this->p作為函數(shù)來調用,正好對應Modifier的__invoke()怜森,不妨將$this->p 設為一個構造好的Modifier對象速挑,
如果這個題目比較友好的話(確實比較友好),這里應該是倒數(shù)第二環(huán)副硅,接下來我們需要找觸發(fā)Test的__get()方法的地方姥宝,
要想到的一點是只能通過源代碼里已有的代碼來觸發(fā),__get()在獲得一個類的成員變量時調用想许,而且一定是$xxx -> 構造好的Test對象 ->xxx的這樣一個形式(因為無法直接$test->xxx)伶授,
由此找到Show類的__toString()魔術方法,
?__toString()在一個對象被當作一個字符串使用時調用流纹,第一反應就是反序列化之后糜烹,echo,這樣就直接觸發(fā)了漱凝,但我們上面提到了疮蹦,這里只給了一步反序列化,沒有echo茸炒、print之類的操作愕乎,還需要繼續(xù)尋找,
本題的最后一個觸發(fā)點隱藏的比較深壁公,在Show類的__wakeup()方法里感论,
?老實講,我看到__wakeup()第一反應(包括第二反應第三反應)是去繞過紊册,而不是利用比肄,但這個題的__wakeup()過濾的是gopher|http一類的內容,對我們構造的pop鏈應該來講沒有影響,畢竟我們只需要找到能一處把Show類的對象當成字符串的地方使用就可以芳绩,這里的本意可能是防止我們通過Show類直接讀认坪ァ(猜的)?
這里的preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)妥色,會將$this->source進行字符串的正則匹配搪花,所以這里自然有一個隱式的“類型轉換”,學過C語言嘹害,我們知道$this->source是不能指向對象自身的撮竿,但可以指向同類的另一個對象,差不多就是這個意思:$show1->source = $show吼拥。
此時$show1->source進行正則匹配倚聚,就會將$show當成字符串,進而觸發(fā)$show的__toString()凿可,只要讓$show的str對象是$test惑折,$test的p為一個Modifier對象,就和上面我們所想連起來了枯跑,
?(不知道有沒有畫錯)
簡陋exp:
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected? $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$modifier = new Modifier();
$test = new Test();
$test -> p = $modifier;
$show = new Show();
$show->str = $test;
$show1 = new Show();
$show1->source = $show;
echo urlencode(serialize($show1));
這里有一個細節(jié)惨驶,就是echo
urlencode(serialize($show1)),因為protected變量經(jīng)反序列化后敛助,變量名為粗卜,\x00 + * + \x00 +
變量名,直接echo payload將其打印到網(wǎng)頁上的話是看不到\00的纳击,復制為參數(shù)達不到效果续扔,
此處一種做法是直接將最終的payload拼接到url里訪問靶機,另一種方法是輸出url編碼后的payload焕数,GET請求的參數(shù)在服務端會自動解碼一次纱昧,故也可以達到效果。
成功堡赔,