0x00 前言
今天做到一題道來自百度杯十二月第四場的ctf題,題目名字叫blog 進階篇,當時沒做出來,看了writeup才知道竟然還有這種騷操作來上傳文件進行包含。
writeup鏈接:https://blog.csdn.net/qq_30123355/article/details/58165038
0x01 題目復現
題目鏈接:https://www.ichunqiu.com/battalion?t=1&r=56951
前面的解題步驟是註冊一個賬號以後,在post.php頁面提交留言內容,這裏會有一個insert into注入,可以拿到管理員的密碼。
利用管理員賬號登錄上去以後會有一個文件包含點,在上級目錄下有flag文件。
正常的思路就是使用僞協議來包含flag.php文件,但是這裏通過kindeditor編輯器的漏洞遍歷文件後嘗試包含,會發現包含不了php後綴的文件,其他後綴名的文件可以正常包含
所以這裏就要用到一個php上傳文件的特性再配合上自包含使得php內存溢出的機制來生成一個webshell文件。
0x02 php文件上傳機制
首先先了解一下php的全局數組$_FILES。
官方的解釋:
通過 HTTP POST 方式上傳到當前腳本的項目的數組。通過使用 PHP 的全局數組 $_FILES,你可以從客戶計算機向遠程服務器上傳文件。
$_FILES 數組提供了多個內容在文件上傳時使用,比較重要的有以下幾個:
$_FILES['myFile']['name'] 客戶端文件的原名稱。
$_FILES['myFile']['type'] 文件的 MIME 類型,需要瀏覽器提供該信息的支持,例如"image/gif"。
$_FILES['myFile']['size'] 已上傳文件的大小,單位爲字節。
$_FILES['myFile']['tmp_name'] 文件被上傳後在服務端儲存的臨時文件名,一般是系統默認。可以在php.ini的upload_tmp_dir 指定,默認是/tmp目錄。
- 這裏的重點就是$_FILES[‘myFile’][‘tmp_name’]這個變量
上傳過程中還利用到了一個重要的函數move_uploaded_file(),該方法是將上傳的文件移動到新位置,若不加上這一行代碼,臨時文件在上傳週期後就被刪除而不會被存儲。
move_uploaded_file(file,newloc)
本函數檢查並確保由 file 指定的文件是合法的上傳文件(即通過 PHP 的 HTTP POST 上傳機制所上傳的)。如果文件合法,則將其移動爲由 newloc 指定的文件。
0x03 上傳測試
在同一目錄下創建兩個文件,file_upload.html和upload.php
- file_upload.html
- upload.php
<?php
echo "上傳前的文件名: ".$_FILES['upload_file']['name'].'</>';
echo "上傳的臨時文件名 : ".$_FILES['upload_file']['tmp_name'].'</br>';
echo "文件類型: ".$_FILES['upload_file']['type'].'</br>';
echo "文件大小: ".($_FILES['upload_file']['size']/1024).' KB</br>';
echo move_uploaded_file($_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['name']);
?>
上傳以後可以看到,tmp_name的命名規則是php[0-9A-Za-z]{3,4},而且在上傳過程中是被臨時存儲在/tmp目錄下(wamp的環境)下。但是上傳完成以後文件會自動被刪除,所以在/tmp下找不到這個文件
- 那麼我們要如何做到讓阻止他將臨時文件刪除呢?這裏就用到了自包含的特性,讓存在php文件包含點的文件包含自己,讓他產生一個相當於死循環的狀態,在包含的過程中我們進行post文件上傳操作。
self_include.php?c=self_include.php
這樣就會導致內存溢出,無法正常結束一個php上傳週期,這時它會清空自己的內存堆棧,以便從錯誤中恢復過來,這時對臨時文件的刪除操作就無法完成,當跳出這個週期後,這個臨時文件就以後綴名爲tmp的形式保存在/tmp目錄下。
這時候我們就利用存在包含點的php文件包含這個臨時文件就行了。
0x04 包含測試
測試環境:apache 2.4.9、php版本5.5.12
1. 創建兩個文件,一個爲存在包含點的self_include.php,一個構造的文件上傳點
- self_include.php
<?php
include($_GET['c']);
?>
- self_include.html
<html>
<meta charset="utf-8">
<body>
<form name="upload" method="post" enctype="multipart/form-data" action="./self_include.php?c=self_include.php">
File: <input type="file" name="file">
<input type="submit" name="submit">
</form>
</body>
</html>
2. 我們讓他自包含和文件上傳同時進行,這裏上傳一個phpinfo文件。
當我們點擊提交以後,發現他報錯了
Maximum function nesting level of ‘100’ reached, aborting!
- 這是因爲在我本地裝了xdebug插件,它默認只能trace 100條的信息,所以這裏在php.ini的xdebug配置下加上一條:
xdebug.max_nesting_level=600
這裏測試過大約包含到150次左右程序就會崩潰,就會在tmp目錄下生成我們需要的臨時文件。
3. 重啓服務器,此時重新包含一次,提交
可以看到這裏生成了兩個臨時文件,說明這裏經過了兩個上傳週期,之後php守護進程無法處理這種情況就會拋出一個無法訪問異常。
4. 之後就可以直接利用包含點,愉快的包含我們上傳的文件了
5. 所以在如果在遠程服務器上的php腳本存在文件包含點的話,我們就可以在本地構造一個html文件,action指向他提交過去就行了。
- 就上面那題來說,最後爲了找到文件名,用了kindeditor編輯器的目錄遍歷漏洞來找到臨時文件的文件名
payload: /kindeditor/php/file_manager_json.php?path=../../../../tmp/
- 在實戰中有其他兩種方式可以包含到臨時文件:
- 使用爆破的方式找出文件名
最容易爆的是三位數字,所以這裏可以多嘗試上傳幾次,直到有三位數字的臨時文件生成。
- 在windows系統下可以使用通配符的方法來包含到臨時文件
由於FindFirstFile的特性,在不確定文件後面字符的情況下,可以使用<<結尾來匹配到這個文件,類似於*。
http://localhost:9000/upload/self_include.php?c=../tmp/php<<
0x05 腦洞大開
就這題來說還有種包含文件getshell的解法,就是包含日誌文件
訪問一個不存在的文件時,會在服務器下的/log/access.log進行記錄,我們可以通過url寫入一個一句話來包含日誌文件,從而getshell
1. 首先訪問http://localhost:9000/<?php phpinfo();?>,記得這裏需要使用bp來發包。
2. 可以看到access.log文件記錄下了我們訪問的url。
3. 進行文件包含,成功包含了phpinfo文件。
http://localhost:9000/upload/self_include.php?c=../logs/access.log
0x06 總結
這裏整個過程需要利用到的點
- 可控的文件包含點。
- 目錄遍歷漏洞。(查看臨時文件名)
重新梳理一下思路
- 構造一個文件上傳點,以post的方式、表單上傳(multipart/form-data)的方式上傳。
- action的url指向存在文件包含漏洞的php文件,接收的參數爲自身文件名(self_include.php?c=self_include.php)。
- self_include.php進行自文件包含的處理,不斷包含自身造成內存溢出。
- php守護進程發出內存溢出信號,清空緩衝區和調用堆棧,以便接收新的請求。
- 一次上傳週期未正常結束,/tmp目錄下的臨時上傳文件得以保留。
- 包含到/tmp目錄下的文件,拿到webshell。