湯青松 中公教育高級(jí)安全工程師
今天分享的PHP反序列化也是異樣安全,每一年都會(huì)爆發(fā)反序列化的問題,PHP反序列化的問題大家會(huì)很少聽到或者是沒有聽過,有幾點(diǎn),首先(英)影響的范圍比較廣泛,PHP反序列化主要是在CMS或者是框架當(dāng)中去做,影響范圍比較少,它的門檻比較高,關(guān)注的人也會(huì)比較少一些,在去漏洞第一時(shí)間會(huì)挖一些(英),應(yīng)用掃描器掃描或者是常見的邏輯性的漏洞,對(duì)于反序列化漏洞一般關(guān)注點(diǎn)相對(duì)來說比較少,但反序列化漏洞有一個(gè)特點(diǎn),如果利用成功了,它的危害往往是比較大的,可以面面執(zhí)行,代碼注入等等。
我今天分享相對(duì)來說比較偏一點(diǎn)的話題,今天有點(diǎn),首先認(rèn)識(shí)漏洞為什么會(huì)產(chǎn)生。第二個(gè)怎么利用它。第三個(gè)拿一個(gè)實(shí)例講解一下。
反序列化漏洞有一些同學(xué)不是太清楚,和反序列化有什么關(guān)系,我相信很多人去淘寶買家具,不會(huì)直接把家具給你運(yùn)過來,會(huì)把這些東西打包拆成一塊一塊,序列化的過程也是一樣的,會(huì)把一個(gè)“類”或者是“數(shù)組”變成這么一個(gè)串,反序列化會(huì)把這個(gè)串再反向操作一下,再把它的變量復(fù)制過去。
認(rèn)識(shí)漏洞,這個(gè)漏洞相對(duì)來說比較偏,首先要知道PHP的對(duì)象,模數(shù)方法,序列化,反序列化,序列化與魔術(shù)方法,以及數(shù)據(jù)結(jié)構(gòu)。
先看一個(gè)代碼,這是一個(gè)非常簡(jiǎn)單的小樣代碼,在代碼當(dāng)中能夠看到一個(gè)class,這是對(duì)象,各種語(yǔ)言基本上都差不多,運(yùn)行之后會(huì)執(zhí)行對(duì)象里面的某一個(gè)方法,魔術(shù)方法,它是在我們不去主動(dòng)調(diào)用某一個(gè)方法自動(dòng)去觸發(fā),在PHP反序列化漏洞當(dāng)中就利用了自動(dòng)觸發(fā)的問題去造成問題,面向?qū)ο缶幊陶Z(yǔ)言會(huì)執(zhí)行它的方法,銷毀一個(gè)對(duì)象的時(shí)候,這些自動(dòng)觸發(fā)的方法可以理解為魔術(shù)方法。
這個(gè)代碼當(dāng)中就是加了下面幾個(gè)方法,這三個(gè)方法,第一個(gè)方法這樣的對(duì)象會(huì)自動(dòng)觸發(fā)它。第二個(gè)就是結(jié)束會(huì)觸發(fā)。第三個(gè)把對(duì)象當(dāng)成錯(cuò)的去觸發(fā),我們先看一下這個(gè)會(huì)自動(dòng)觸發(fā)方法。
第二個(gè)是手動(dòng)調(diào)用方法,后面把對(duì)象作為字符串自動(dòng)輸出,最后程序要結(jié)束,這是運(yùn)行結(jié)果,這是一個(gè)反序列化漏洞主要的關(guān)鍵點(diǎn)。
假設(shè)還是有一個(gè)類,會(huì)有兩個(gè)變量,這里面會(huì)有兩個(gè)方法,有了這樣一個(gè)對(duì)象,把對(duì)象兩個(gè)變量復(fù)制,調(diào)用了一下這個(gè)方法,這邊會(huì)輸出字符串,再把它進(jìn)行序列化操作,輸出出來,輸出的結(jié)果這個(gè)是程序當(dāng)中(英)的值,這是反序列化的數(shù)據(jù),把反序列化的數(shù)據(jù)復(fù)制出來,再認(rèn)識(shí)一下反序列的操作,這個(gè)代碼當(dāng)中同樣的代碼不變,下面不去做這個(gè)方法,把它的序列化數(shù)據(jù)放在這里面,反序列化操作一下,得到的還是這樣的類,并且在序列化類的過程當(dāng)中會(huì)把變量給復(fù)制進(jìn)去,這邊并沒有給進(jìn)行復(fù)制操作,依然是零,這邊還是一個(gè)空的,當(dāng)我們執(zhí)行這個(gè)方法我們輸出的結(jié)果這個(gè)是我們這邊的名字,這是運(yùn)行結(jié)果。
反序列化操作會(huì)做兩個(gè)事情,這邊有一個(gè)自定義的方法,后邊都是魔術(shù)方法,在自動(dòng)觸發(fā)的時(shí)候,來認(rèn)識(shí)一下這幾個(gè)魔術(shù)方法,序列化也會(huì)觸發(fā)這樣的方法,當(dāng)我們輸出序列化字符,在這邊進(jìn)行反序列化操作的時(shí)候又會(huì)調(diào)用這樣的魔術(shù)方法,在反序列化漏洞操作的時(shí)候,在迂回的不斷嘗試這幾個(gè)方法的過程,接下來看一下例子,這是剛才代碼的運(yùn)行結(jié)果,可以看到這邊的魔術(shù)方法都是自動(dòng)執(zhí)行的,序列化的結(jié)構(gòu),這里面定了一個(gè)數(shù)組,數(shù)組當(dāng)中進(jìn)行了序列化操作,并且把序列化操作的數(shù)據(jù)給輸出出來了,PHP序列化結(jié)構(gòu)是什么,3是代表數(shù)組,有3個(gè)數(shù)據(jù),ABC,這邊有一個(gè)S1,就代表字符串,1是它的長(zhǎng)度,內(nèi)容是A,大概是這樣的結(jié)構(gòu),這邊還有一些特征,我們一會(huì)兒用到對(duì)象的時(shí)候它可能是O,代表的是對(duì)象。
接下來看一下怎么去利用的,我們?cè)谶@個(gè)代碼當(dāng)中有兩類,第一個(gè)類,第二個(gè)類,下面有一行代碼,這行代碼不在這兩個(gè)類當(dāng)中,代碼是有漏洞的,怎么利用,在執(zhí)行之后前端傳遞了一個(gè)(英),通過(英)傳遞了一個(gè)數(shù)據(jù),這里并沒有進(jìn)行安全過濾,就把它進(jìn)行到了反序列化操作,這里就會(huì)觸發(fā)一個(gè)操作,在這個(gè)類里會(huì)自動(dòng)觸發(fā)這個(gè)魔術(shù)方法,又會(huì)調(diào)用自定義方法,這個(gè)方法當(dāng)中又會(huì)調(diào)用這個(gè)對(duì)象,這個(gè)對(duì)象當(dāng)前類并沒有這樣的對(duì)象,它是一個(gè)變量,怎么做?我們可以把變量復(fù)制為對(duì)象,可以把這樣的變量復(fù)制為這樣的對(duì)象,在這個(gè)對(duì)象里面又可以把PHP設(shè)置為我們想執(zhí)行的命令,調(diào)用這樣的方法就執(zhí)行了相應(yīng)的命令,看一下POC,第一步我們?cè)谶@邊再寫一個(gè)POC的代碼,讓程序識(shí)別,在這個(gè)對(duì)象當(dāng)中把它進(jìn)行復(fù)制,這樣的變量在識(shí)別的時(shí)候會(huì)調(diào)用識(shí)別方法,自動(dòng)給變量復(fù)制,復(fù)制的時(shí)候又把下一個(gè)對(duì)象放進(jìn)去,下一個(gè)對(duì)象在方法里面也放了一個(gè)(英),這樣的值就是(英),這個(gè)變量里面的數(shù)據(jù)就是一個(gè)對(duì)象,運(yùn)行出來之后就會(huì)輸出這樣的字符,當(dāng)把這樣的字符放到前面代碼傳遞過去就會(huì)執(zhí)行相應(yīng)的操作,把結(jié)果放進(jìn)去,因?yàn)闀?huì)執(zhí)行(英),再通過反序列化操作就會(huì)產(chǎn)生這樣的結(jié)果。
我們看一個(gè)實(shí)例,這個(gè)實(shí)例在PHP里面比較流行的博客系統(tǒng),之前爆發(fā)了一個(gè)代碼的漏洞,這個(gè)漏洞通過反序列化操作的過程,分析一下漏洞的利用過程,我們說到PHP反序列化操作,就是使用反序列化函數(shù),把字符串放進(jìn)去,當(dāng)這個(gè)函數(shù)去調(diào)用了,就可能產(chǎn)生反序列化的漏洞,在這個(gè)博客當(dāng)中直接去搜索函數(shù),在安裝的位置這個(gè)地方有這樣的一串代碼,這個(gè)代碼我們可以看到只做了三個(gè)事情,首先從cookie取出數(shù)據(jù),通過base64解碼后進(jìn)行反序列化操作,從前端傳遞出來數(shù)據(jù)沒有進(jìn)行安全過濾就放在反序列化操作,就會(huì)產(chǎn)生安全問題,有這樣的安全問題最主要是看它能不能利用,如果有一個(gè)漏洞,但是這個(gè)漏洞并不能進(jìn)行利用,那就說明沒有什么效果,反序列化操作的時(shí)候會(huì)調(diào)用特征方法,反序列化操作的時(shí)候會(huì)捋一個(gè)對(duì)象,會(huì)調(diào)用反序列化操作方法,會(huì)調(diào)用一個(gè)魔術(shù)方法,在進(jìn)行反序列化漏洞的時(shí)候有兩種思路,第一個(gè)就是直接利用,只要進(jìn)行反序列化操作必定會(huì)執(zhí)行這三個(gè)方法,這是反序列化的魔術(shù)方法以及對(duì)象結(jié)束的方法,我們就可以搜索全系統(tǒng)當(dāng)中有哪些位置有這幾個(gè)方法,看這三個(gè)方法有沒有對(duì)它當(dāng)前類里面的變量進(jìn)行處理,使用(英)處理當(dāng)前的變量或者是其他的方法,如果有的話就會(huì)有問題,反序列化操作是可以控制某一個(gè)類里面的變量數(shù)據(jù),在處理變量的時(shí)候就會(huì)產(chǎn)生相應(yīng)的問題。
另外一個(gè)就是間接利用,去了一個(gè)迷宮,去迷宮有兩種結(jié)果,第一種我們直接找到出口。另外一種我們并不能一下子找到出口我們會(huì)在里面繞,一直找到出口為止,我們?cè)谥苯永玫臅r(shí)候不能用,就會(huì)對(duì)代碼進(jìn)行跟進(jìn),首先會(huì)排除這三個(gè)方法再去找其他可以間接利用的方法,比如tostring,(英)等方法。
回到這個(gè)代碼當(dāng)中,這個(gè)代碼當(dāng)中按照直接利用思路會(huì)搜索魔術(shù)方法,在全局系統(tǒng)里面去搜這幾個(gè),在系統(tǒng)當(dāng)中就找到了這兩處位置有方法,但是并沒有對(duì)變量進(jìn)行處理,這個(gè)位置并不適用,直接利用法把它放棄了,接下來就需要迂回的去看這個(gè)系統(tǒng)當(dāng)中還有沒有其他的安全問題,間接利用我們除了直接利用的那三個(gè)魔術(shù)方法,跟進(jìn)代碼看它有沒有把一個(gè)對(duì)象進(jìn)行字符串操作,比如他把一個(gè)對(duì)象進(jìn)行字符串操作的時(shí)候就會(huì)去觸發(fā)(英)方法,也有可能在調(diào)用變量的時(shí)候調(diào)用到不可訪問的變量,就會(huì)觸發(fā)另外一個(gè)(英)的魔術(shù)方法,我們接著對(duì)代碼進(jìn)行審計(jì),在剛才那串代碼的時(shí)候看到它下面又把反序列化數(shù)據(jù)了,里面有兩個(gè)變量放在這里面,我們就根據(jù)這個(gè)方法里面的處理,這里面?zhèn)鬟f了一個(gè)(英),首先看這樣一個(gè)變量,它確實(shí)是把這樣的變量作為字符串處理,假設(shè)我們把這樣的變量是作為對(duì)象,并沒有進(jìn)行安全過濾,把它作為對(duì)象,就會(huì)觸發(fā)(英)的魔術(shù)方法,什么地方有(英)方法,只要在全局系統(tǒng)當(dāng)中搜索一下,搜索的時(shí)候就搜索(英),搜索有三處進(jìn)行(英)操作,雖然我們找到了這些方法,但這些方法有沒有對(duì)變量進(jìn)行相應(yīng)的處理,前面兩處是沒有進(jìn)行任何處理的,最后一處他是有對(duì)對(duì)象進(jìn)行處理,我們繼續(xù)跟進(jìn),處理的時(shí)候并沒有直接對(duì)某一個(gè)變量進(jìn)行安全的過濾或者是進(jìn)行相應(yīng)的轉(zhuǎn)換,它是又進(jìn)行了一次迂回,就像我們?nèi)ッ詫m,迷宮我們找到一個(gè)房間,這個(gè)房間發(fā)現(xiàn)并不是一個(gè)出口,還有一條門就繼續(xù)跟進(jìn)這條門,前面直接利用的三個(gè)方法就排除了,再加上(英)只找到當(dāng)前類可以利用的,(英)也排除了,還剩下哪幾個(gè)魔術(shù)方法可以利用,可能會(huì)有set、isset這幾個(gè)魔術(shù)方法可以被利用,我們的(英)有沒有調(diào)用不可訪問的代碼,繼續(xù)跟進(jìn),這三行代碼需要注意,這三行代碼有什么樣的特點(diǎn),首先調(diào)用了當(dāng)前變量類里面的變量,反序列化操作是可以知道它的結(jié)構(gòu),把這樣的變量定義為數(shù)組,里面某一個(gè)(英)值再給它一個(gè)對(duì)象,并且這樣的對(duì)象是不存在這樣的值。這樣的一個(gè)類還要滿足一個(gè)條件,這個(gè)類必須要有一個(gè)(英)魔術(shù)方法,繼續(xù)接著去迂回,搜索,在這些系統(tǒng)當(dāng)中有哪些get的魔術(shù)方法,全局搜索之后又搜索出三個(gè)位置有g(shù)et的方法,有兩處是沒有意義的,因?yàn)椴⒉荒苷{(diào)用當(dāng)前類里面的變量進(jìn)行處理,因?yàn)椋ㄓⅲ┧?,我們可以看到它這邊又調(diào)用了一個(gè)get,把變量返回,我們繼續(xù)跟進(jìn)get的方法,跟進(jìn)之后,我們看看這個(gè)代碼,在這個(gè)方法里面他有一個(gè)案子判斷一下當(dāng)前類里面的變量是否存在,如果存在的時(shí)候就取里面的值,這個(gè)變量我們是可以控制的,我們?cè)诜葱蛄谢僮鞯臅r(shí)候肯定會(huì)把變量設(shè)置成這樣的值,我們是可以控制會(huì)走這樣的一行代碼,它上面還有一行代碼,這行代碼會(huì)判斷當(dāng)前變量是數(shù)組還是字符串,以及進(jìn)行相應(yīng)的過濾,這里做了一個(gè)安全過濾,這里面就有一處問題了,首先會(huì)判斷(英)有沒有(英),(英)是否存在,我們是可以控制這樣類里面的方法值,可以讓它找這里面的方法,使用哪些方法對(duì)某一個(gè)值進(jìn)行過濾,既然(英)可以控制,在(英)我們可以填入(英),繼續(xù)判斷這個(gè)值是數(shù)組還是對(duì)象,使用不同的方法把(英)對(duì)值進(jìn)行過濾,把這樣的(英)放進(jìn)去,(英)可以控制,我們可以有一個(gè)任意代碼執(zhí)行的問題,接著根據(jù)我們剛才的思路,我們構(gòu)造一個(gè)POC,在構(gòu)造POC的時(shí)候,主要有四點(diǎn),首先我們可以看到取數(shù)據(jù)的位置取得(英)數(shù)據(jù),取得的是64的數(shù)據(jù),POC就輸出一個(gè)64的數(shù)據(jù),這是最外面的包裝,里面調(diào)用了兩個(gè)變量,這里就給他兩個(gè)變量,在前面有一個(gè)方法把某一個(gè)變量當(dāng)做字符串,為了讓它觸發(fā)(英)方法,我們就給他放一個(gè)對(duì)象,當(dāng)他把數(shù)據(jù)作為字符串處理就做成(英)方法,這個(gè)方法里面有什么?這個(gè)是POC,前面調(diào)用了一個(gè)不存在的方法,這個(gè)不存在的方法是有屬性的,調(diào)用了這樣的方法我們又給他放入了相應(yīng)的對(duì)象,這個(gè)對(duì)象就是(英)的對(duì)象,主要給它設(shè)置兩個(gè)值,第一個(gè)設(shè)置了(英)值,我們想執(zhí)行某一個(gè)函數(shù),是動(dòng)態(tài)的。第二個(gè)會(huì)獲取變量,前面那個(gè)代碼當(dāng)中調(diào)用了變量,把相應(yīng)的值傳進(jìn)去,結(jié)果就會(huì)產(chǎn)生這樣的效果,就是(英)里面/1,就會(huì)被執(zhí)行出來,也就是這樣的POC就構(gòu)造成功了。
剛才復(fù)盤的時(shí)候,這邊取的是(英)64,這邊輸出了64,為了讓它觸發(fā)(英)方法,因?yàn)檫@個(gè)地方是把它作為字符串處理,我們這個(gè)地方放入一個(gè)對(duì)象就可以調(diào)用這里面的方法,在調(diào)用(英)方法這里面使用了構(gòu)造的魔術(shù)方法,這些本來應(yīng)該是字符串的數(shù)據(jù),結(jié)果又把它放入了另外一個(gè)類,在調(diào)用這樣數(shù)據(jù)的時(shí)候,結(jié)果發(fā)現(xiàn)這樣的類是找不到的,這樣的類本來是可以找到的數(shù)據(jù),結(jié)果忙入這樣的類,這樣的參數(shù)反而是私有方法,找不到,又會(huì)調(diào)用get方法,調(diào)用get方法又會(huì)觸發(fā)構(gòu)造方法,環(huán)環(huán)相扣,就執(zhí)行了(英),傳遞出了一個(gè)/1。(速記)