目錄
一、白名單過濾
二、%00截斷
1、%00截斷原理
2、空字符
3、截斷條件
(1)PHP版本 < 5.3.4
(2)magic_quotes_gpc配置為Off
(3)代碼邏輯存在缺陷
三、源碼分析
1、代碼審計
(1)文件存儲依賴字符串截斷
(2)路徑拼接可控
2、攻擊思路
四、滲透實戰
1、構建腳本test12.jpg
2、配置php服務
(1)安裝php5.3.4以下的php版本
(2)切換php5.3.4以下的php版本
(3)關閉magic_quotes_gpc配置
(4)重啟Apache服務
3、打開靶場
4、bp開啟攔截
5、點擊上傳
6、bp攔截
7、GET參數增加test12.php%00
8、發包并獲取腳本地址
10、訪問腳本
本文通過《upload-labs靶場通關筆記系列》來進行upload-labs靶場的滲透實戰,本文講解upload-labs靶場第12關白名單GET法滲透實戰。
一、白名單過濾
在文件上傳功能中,白名單過濾的核心思想是"只允許明確許可的,其他一律禁止",這與黑名單的"禁止已知危險的"形成鮮明對比。文件上傳白名單過濾是一種嚴格的安全機制,它通過預先定義一組允許上傳的文件擴展名(如jpg、png、pdf等),系統僅接受符合白名單的文件類型,其他所有類型一律拒絕。相比黑名單過濾,白名單采用"默認禁止"原則,從根本上杜絕了攻擊者通過雙寫、大小寫變異、特殊擴展名等手法繞過防御的可能性,顯著提高了安全性。黑名單過濾方法和白名單的過濾方法的區別具體如下圖所示。
對比維度 | 白名單機制 | 黑名單機制 |
---|---|---|
安全原則 | 只允許明確許可的文件類型(默認拒絕所有) | 僅禁止已知危險文件類型(默認允許所有) |
安全性 | ★★★★★(難以繞過) | ★★☆☆☆(易被雙寫/大小寫等手法繞過) |
典型實現 | in_array(strtolower(ext), ['jpg','png']) | str_ireplace(['php','jsp'], "", filename) in_array(strtolower(ext), ['php','phtml']) |
維護成本 | 需隨業務需求更新白名單 | 需持續追蹤新增危險擴展名 |
防御效果 | 能防御未知攻擊類型 | 僅能防御已知攻擊類型 |
常見繞過方式 | 很難繞過 | 雙寫(.pphphp)、大小寫(.PhP)、空字節等 |
適用場景 | 嚴格管控的穩定業務(如頭像上傳) | 需支持動態擴展名的特殊場景 |
二、%00截斷
1、%00截斷原理
%00截斷是一種利用字符串處理特性和編程語言安全性風險的攻擊手段,%00截斷的原理如下:
- 字符編碼與字符串結束符:在許多編程語言中,
\0
(在 URL 編碼中表示為%00
)被用作字符串的結束符。當程序處理字符串時,遇到\0
就會認為字符串在此處結束。 - 文件名或路徑處理安全問題:如果程序在處理文件名或文件路徑時,沒有對用戶輸入進行嚴格的過濾和驗證,就可能導致
%00
截斷攻擊。例如,攻擊者上傳一個名為test12.php%00.jpg
的文件,程序在拼接文件保存路徑時,可能會將%00
之后的內容截斷,實際保存的文件路徑就變成了test12.php
,從而繞過了文件類型檢查等安全機制。
2、空字符
在很多編程語言中,字符串是以字符數組的形式存儲的,并且以\0(ASCII 碼值為 0)作為字符串的結束標志。當程序對字符串進行處理時,遇到\0就會認為字符串到此結束。%00截斷主要利用的是 ASCII 碼為 0 的空字符\0(在 URL 中表示為%00)來實現字符串截斷,一般來說就是這一個特定字符起截斷作用。0x00,%00,/00之類的截斷,都是一樣的,只是不同表示而已。
字符 | ASCII 碼值 | 十六進制表示 | URL 編碼表示 | 作用 |
---|---|---|---|---|
\0 | 0 | 0x00 | %00 | 在%00 截斷攻擊中,用于截斷字符串,使服務器將其后面的字符忽略,從而達到繞過文件類型檢查等目的 |
3、截斷條件
(1)PHP版本 < 5.3.4
-
PHP 5.3.4之前:默認會解析
%00
(空字節)為字符串終止符,導致截斷。 -
PHP 5.3.4及之后:修復了
%00
自動截斷的問題,但仍需注意代碼邏輯缺陷。
(2)magic_quotes_gpc配置為Off
- magic_quotes_gpc用于自動對來自GET、POST和COOKIE的數據進行轉義,在一些特殊字符(如單引號、雙引號、反斜杠和空字符等)前添加反斜杠\。其目的是防止 SQL 注入等攻擊,確保數據在數據庫操作中的安全性。
- 當magic_quotes_gpc為Off時,對用戶輸入的數據不再進行自動轉義處理。這意味著攻擊者輸入的%00字符可以直接傳遞到程序中,而不會被轉義為\%00。如果程序在處理文件上傳或其他涉及字符串處理的功能時,沒有對%00進行額外的過濾,就更容易被利用來進行%00截斷攻擊。例如,在前面提到的文件上傳代碼中,如果magic_quotes_gpc為Off,攻擊者構造的evil.php%00.jpg文件名可以直接被程序接收,進而可能導致文件類型檢查被繞過。
(3)代碼邏輯存在缺陷
-
未過濾
%00
:代碼直接使用$_FILES['file']['name']
或用戶輸入拼接路徑,未做空字節過濾。 -
未規范化路徑:未使用
basename()
、realpath()
等函數處理文件名,導致%00
被保留。
三、源碼分析
1、代碼審計
對upload-labs 第12關的源碼進行審計,相對于第3-11關卡的黑名單過濾,本關卡變為了白名單過濾,僅允許文件后綴名為“jpg,png和gif”三種后綴的文件上傳,具體如下所示。
經過詳細注釋的代碼如下所示。
<?php
// 初始化變量,用于標記文件是否上傳成功,初始值為 false
$is_upload = false;
// 初始化變量,用于存儲上傳過程中的提示信息,初始值為 null
$msg = null;// 檢查是否通過 POST 方式提交了名為 submit 的表單數據
if(isset($_POST['submit'])){// 定義一個數組,包含允許上傳的文件擴展名$ext_arr = array('jpg','png','gif');// 獲取上傳文件的原始文件名,從最后一個點號(.)之后的部分作為文件擴展名$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);// 檢查獲取到的文件擴展名是否在允許上傳的擴展名數組中if(in_array($file_ext,$ext_arr)){// 獲取上傳文件在服務器臨時存儲的路徑$temp_file = $_FILES['upload_file']['tmp_name'];// 拼接上傳文件在目標目錄中的完整路徑// $_GET['save_path'] 從 URL 的 GET 參數中獲取保存路徑// rand(10, 99) 生成一個 10 到 99 之間的隨機數// date("YmdHis") 獲取當前的日期和時間,格式為年月日時分秒// 最后拼接上文件擴展名$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;// 嘗試將臨時文件移動到目標目錄if(move_uploaded_file($temp_file,$img_path)){// 如果移動成功,將 $is_upload 標記為 true$is_upload = true;} else {// 如果移動失敗,設置提示信息為上傳出錯$msg = '上傳出錯!';}} else{// 如果文件擴展名不在允許的列表中,設置提示信息為只允許上傳特定類型的文件$msg = "只允許上傳.jpg|.png|.gif類型文件!";}
}
?>
分析代碼可知存在的白名單%00截斷安全問題,具體原因如下所示。
(1)文件存儲依賴字符串截斷
文件存儲的過程使用了move_uploaded_file()這個C底層函數:PHP的文件操作函數(如move_uploaded_file()底層是C語言實現,會按\0截斷字符串。
(2)路徑拼接可控
攻擊者能控制文件保存路徑或文件名的一部分(如通過$_GET的save_path參數傳入)。?\0(在 URL 編碼中表示為?%00)被視為字符串的結束符。
此代碼在拼接文件保存路徑時,使用了?$_GET['save_path']?從 URL 的 GET 參數中獲取保存路徑,并且沒有對這個參數進行嚴格的過濾和驗證。
2、攻擊思路
在文件上傳功能中,如果程序只是簡單地通過檢查文件名的后綴來驗證文件類型,而沒有對整個文件名進行嚴格的安全檢查。假設上傳的圖片名為$_FILES['upload_file']['name']=test12.jpg,save_path為“../upload/”。
-
攻擊者構造$_GET參數傳入的save_path包含腳本名與%00拼接的特殊文件名。
save_path:../upload/test12.php%00
-
前端驗證看到的是".jpg"擴展名,file_ext為“jpg”,可以通過檢查
$_FILES['upload_file']['name']:test12.jpg
file_ext:jpg
-
由于$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;得知圖片的路徑為“test12.php%00”與“/”和“rand(10, 99).date("YmdHis").”和".jpg"的結合。假設rand(10, 99).date("YmdHis")的輸出為8820250428,那么
save_path:../upload/test12.php%00
重命名后的圖片:8820250428.jpg
img_path:../upload/test12.php%00/8820250428.jpg
-
后端move_uploaded_file()處理時,遇到%00即停止解析,實際存儲的文件名變為"test12.php"
img_path:../upload/test12.php
四、滲透實戰
1、構建腳本test12.jpg
注意此時文件的后綴為jpg,這是因為本關卡為白名單,故腳本的后綴需要與上傳允許的后綴“jpg,png和gif”中的任意一個保持一致。
<?php
phpinfo();
?>
2、配置php服務
需要將php版本號切換到5.3.4以下,具體步驟如下所示。
(1)安裝php5.3.4以下的php版本
如果當前phpstudy沒有安裝5.3.4以下的版本,需要安裝對應版本的php,如下所示。
(2)切換php5.3.4以下的php版本
網站——>管理——>php版本——>選擇php5.3.4以下的php版本,具體如下所示。
(3)關閉magic_quotes_gpc配置
打開phpstudy保存目錄——>選擇Extensions目錄——>php目錄——>選擇php5.2.17nts版本——>找到php.ini文件
將magic_quotes_gpc值修改為off,修改前如下所示。
將magic_quotes_gpc值修改為off,修改后如下所示。
(4)重啟Apache服務
修改后需要重啟小皮的Apache服務,具體如下所示。
3、打開靶場
?打開靶場第12關,瀏覽選擇該腳本,但不點擊上傳。
4、bp開啟攔截
5、點擊上傳
6、bp攔截
bp捕獲到上傳報文,下圖紅框的部分即為需要修改的部分。
這時候注意,雖然這個報文為POST報文,但是savepath確實在GET參數中,具體如下所示。
POST /upload-labs/Pass-12/index.php?save_path=../upload/ HTTP/1.1
由于需要將savepath的"save_path=../upload/"后綴改為"save_path=../upload/test12.php%00",修改之前文件名為"test12.php",如下所示。?
POST /upload-labs/Pass-12/index.php?save_path=../upload/test12.php%00 HTTP/1.1
7、GET參數增加test12.php%00
將save_path改為"../upload/test12.php%00",修改后效果如下所示。
8、發包并獲取腳本地址
將bp的inception設置為off,此時修改后的報文發送成功。
回到靶場的Pass12關卡,圖片已經上傳成功,在圖片處右鍵復制圖片地址。
右鍵圖片獲取圖片地址,如下所示獲取到圖片URL。
http://127.0.0.1/upload-labs/upload/test12.php /9620211121160947.jpg
但由于%00截斷,可將test12.php之后的內容截斷,因此上傳的腳本url地址變為:
http://127.0.0.1/upload-labs/upload/test12.php
以本實驗為例我們上傳文件時修改的文件名是../upload/test12.php%00
上傳成功后的路徑和文件名為upload/test12.php /9620211121160947.jpg
很明顯圖片被上傳到upload目錄中且test12.php被重命名為test12.php /9620211121160947.jpg
但由于%00截斷,可將test12.php之后的內容截斷,所以最后得到的文件名為test12.php
10、訪問腳本
?如下所示訪問test12.php腳本獲取到服務器的php信息,證明文件上傳成功。