挖洞經驗 | 記一次曲折的Getshell過程
最近在挖某框架的漏洞,其中挖到一枚Getshell,挖的過程有點曲折感覺可以寫篇文章總結一下,方便與各位大牛交流交流。
因為此框架有大量使用者,並且此漏洞並未修復,故此隱去所有有關此框架的資訊,連文章中出現的程式碼都是我自己另寫的,重在思路,希望大家理解。
首先通過審計定位到可能導致漏洞的程式碼(路徑:/edit/creat.php):
其中 $GP 是合併 $GET 和 $_POST 的變數。
可以看到寫入的檔案路徑和寫入的部分內容都是可控的,看到這裡不禁露出了一絲笑容,沒想到一枚 getshell 如此輕鬆。
好吧,先測試一下,把$file的值設定為:
<?php echo 1111; ?>.php
post 到 /index.php? control=edit&action=creat
(此框架是單入口)
預計生成的檔案內容是:
好了,那訪問一下生成的檔案,URL:
右鍵檢視一下原始碼,發現輸出的內容是:
居然被過濾了? 回溯之前的程式碼,在 index.php 檔案中發現程式碼:
關鍵在開始的兩行程式碼上,htmlEncode ? 搜尋這個函式,找到這個函式的程式碼如下:
結合起來,就是對 post 和 get 獲取到的所有內容進行htmlspecialchars,所以才會出現上面所看到的尖括號被過濾的情況。
看到這裡,臉上的笑容都消失了,哎呀,果然沒那麼容易。尖括號過濾了,那就沒辦法寫入PHP 程式碼的解析標籤了,想不到什麼突破的辦法,難道就這樣放棄麼?開始犯愁…
一直想著:過濾了尖括號怎麼辦?過濾了尖括號怎麼辦?過濾了尖括號怎麼辦……
那我能不能不用尖括號呢?不用尖括號能不能解析?要怎麼才能解析?想到這裡,突然就想到模板!這個框架的模板和大多數 MVC 的模板一樣,使用大括號作為標記:
這樣就可以使用模板的標記 {} 來繞過尖括號 <> 的過濾,但是根據這個框架的路由協定,模板不能隨便被包含,所以只能覆蓋原有的模板。
按照這個思路,找一個有載入模板的功能,覆蓋載入的模板,覆蓋之後訪問了就可以解析了。按照這個思路,找到一個載入了模板的功能,URL是:
/index.php? control=basic&action=index
程式碼路徑在/basic/index.php,程式碼最後就有呼叫 view(‘index’);
載入的模板路徑在:
/themes/basic/index.html
按照這些資訊,應該構造 $file 的值為:
../../themes/basic/index.html
但是這樣又有一個問題了,雖然構造這樣的值可以覆蓋原有的模板檔案,但是寫入的檔案內容就是:
這樣的話就沒有寫入需要的 Webshell 了,怎麼辦呢?!
根據 URL 的特性,./1.php 和 ./test/../1.php 訪問的內容是一樣的,都是 1.php 這個檔案,但是 test 這個目錄名我是可以隨便寫的,再根據模板虛擬碼的格式構造一個控制 $file 的測試 POC:
(根據 view() 函式的程式碼,有一個{php }虛擬碼標籤,處理的時候會替換為 <?php >。其實就算是沒有這標籤也可以用其他非組合的標籤代替)
生成的檔案內容為:
訪問 URL:
/index.php? control=basic&action=index
右鍵檢視原始碼,輸出的內容為:
證明程式碼執行了,那構造一個包含一句話的 POC,按照上一個 POC 的思路,應該把 file 的值構造為:
但是訪問後發現輸出的內容為:
想起 $file 的值是通過框架封裝的 $GP 的值都經過了 htmlencode,怎麼辦呢?! 根據 PHP 的特性,$_POST[‘w’] 獲取值的時候可以把引號去掉,所以可以把 poc 改為:
訪問 URL:/index.php? control=basic&action=index ,給引數 w傳值 echo 1111;
右鍵檢視原始碼,內容為:
證明 post 的程式碼確實被執行了,到伺服器上執行:
至此,getshell 完成,雖然過程有點艱辛,但還是拿下了。
最後總結一下:
1. 剛開始遇到過濾尖括號等的 HTML 字元的時候,利用了 MVC 模板中的虛擬碼代替繞過了 2. 遇到覆蓋檔案時候填寫完整路徑不能寫入payload 的問題,使用了構造一個不存在的目錄(目錄的名稱就是 payload)的方法進行 payload 的寫入 3. 最後寫入 payload 的時候發現也過濾了引號導致不能寫入 $POST[‘w’] ,根據 PHP 的特性,去掉引號依然可以獲取 w 下標的內容,所以替換為 $POST[w] 4. 寫入最終構造好的 payload,Getshell 完成!