ileUpload 對象
在網頁上傳文件,最核心元素就是這個HTML DOM的FileUpload對象了。什么鬼?好像不太熟啊~別急,看到真人就熟了:
<input type="file">
就是他啊!其實在 HTML 文檔中該標簽每出現一次,一個 FileUpload 對象就會被創建。該標簽包含一個按鈕,用來打開文件選擇對話框,以及一段文字顯示選中的文件名或提示沒有文件被選中。
把這個標簽放在<form>標簽內,設置form的action為服務器目標上傳地址,并點擊submit按鈕或通過JS調用form的submit()方法就可以實現最簡單的文件上傳了。
<form id="uploadForm" method="POST" action="upload" enctype="multipart/form-data">
<input type="file" id="myFile" name="file"></input>
<input type="submit" value="提交"></input>
</form>
這樣就完成功能啦?沒錯。但是你要是敢提交這樣的代碼,估計臉要被打腫
都什么年代了,我們要的是頁面無刷新上傳!
更優雅的上傳
現代網頁通過什么來實現用戶與服務器的無刷新交互?
——XMLHttpRequest
對,就是這個你很熟悉的家伙。如果你開發的產品支持的瀏覽器是現代瀏覽器,那么恭喜你,文件上傳就是這么easy!特別強調強調現代瀏覽器是因為我們接下來討論的XMLHttpRequest指的是XMLHttpRequest Level 2。
那什么是Level 1?為什么不行?因為它有如下限制:
而XMLHttpRequest Level 2針對這些缺陷做出了改進:
關于XMLHttpRequest的細節就不在這里贅述了,有興趣可以移步這篇博客。目前, 主流瀏覽器基本上都支持XHR2, 除了IE系列需要IE10及更高版本. 因此IE10以下是不支持XHR2的.
上面提到的FormData就是我們最常用的一種方式。通過在腳本里新建FormData對象,把File對象設置到表單項中,然后利用XMLHttpRequest異步上傳到服務器:
<form id="uploadForm" method="POST" action="upload" enctype="multipart/form-data">
<input type="file" id="myFile" name="file"></input>
<input type="submit" value="提交"></input>
</form>
完成最基本的需求無法滿足我們對用戶體驗的追求,所以我們還想要支持上傳進度顯示和上傳圖片預覽。
上傳進度
因為是XMLHttpRequest Level 2, 所以很容易就可以支持對上傳進度的監聽。細心地小伙伴會發現在chrome的developer tools的console里new一個XHR對象,調用點運算符就可以看到智能提示出來一個onprogress事件監聽器,那是不是我們只要綁定XHR對象的progress事件就可以了呢?
很接近了,但是XHR對象的直屬progress事件并不是用來監聽上傳資源的進度的。XHR對象還有一個屬性upload, 它返回一個XMLHttpRequestUpload 對象,這個對象擁有下列下列方法:
這些方法在XHR對象中都存在同名版本,區別是后者是用于加載資源時,而前者用于資源上傳時。其中onprogress 事件回調方法可用于跟蹤資源上傳的進度,它的event參數對象包含兩個重要的屬性loaded和total。分別代表當前已上傳的字節數(number of bytes)和文件的總字節數。比如我們可以這樣計算進度百分比:
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
var percentComplete = (event.loaded / event.total) * 100;
// 對進度進行處理 }
}
其中事件的lengthComputable屬性代表文件總大小是否可知。如果 lengthComputable 屬性的值是 false,那么意味著總字節數是未知并且 total 的值為零。
如果是現代瀏覽器,可以直接配合HTML5提供的
<progress id="myProgress" value="50" max="100">
</progress>
其value屬性綁定上面代碼中的percentComplete的值即可。再進一步我們還可以對<progress>的樣式統一調整,實現優雅降級方案,具體參見這篇文章。
再說說我在測試這個progress事件時遇到的一個問題。一開始我設在onprogress事件回調里的斷點總是只能走到一次,并且loaded值始終等于total。覺得有點詭異,改用console.log打印loaded值不見效,于是直接加大上傳文件的大小到50MB,終于看到了5個不同的百分比值。
因為xhr.upload.onprogress在上傳階段(即xhr.send()之后,xhr.readystate=2之前)觸發,每50ms觸發一次。所以文件太小網絡環境好的時候是直接到100%的。
圖片預覽
普通青年的圖片預覽方式是待文件上傳成功后,后臺返回上傳文件的url,然后把預覽圖片的img元素的src指向該url。這其實達不到預覽的效果和目的。
屬于文藝青年的現代瀏覽器又登場了:“使用HTML5的FileReader API吧!” 讓我們直接上代碼,直奔主題:
function handleImageFile(file) {
var previewArea = document.getElementById('previewArea');
var img = document.createElement('img');
var fileInput = document.getElementById("myFile");
var file = fileInput.files[0];
img.file = file;
previewArea.appendChild(img);
var reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) {
aImg.src = e.target.result;
}
})(img);
reader.readAsDataURL(file);
}
這里我們使用FileReader來處理圖片的異步加載。在創建新的FileReader對象之后,我們建立了onload函數,然后調用readAsDataURL()開始在后臺進行讀取操作。當圖像文件加載后,轉換成一個 data: URL,并傳遞到onload回調函數中設置給img的src。
另外我們還可以通過使用對象URL來實現預覽:
var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);;
img.onload = function() {
// 明確地通過調用釋放
window.URL.revokeObjectURL(this.src);
}
previewArea.appendChild(img);
多文件支持
什么?一個一個添加文件太煩?別急,打開一個開關就好了。別忘了我們文章一開頭就登場的FileUpload對象,它有一個multiple屬性。只要這樣
<input id="myFile" type="file" multiple>
我們就能在打開的文件選擇對話框中選中多個文件了。然后你在代碼里拿到的FileUpload對象的files屬性就是一個選中的多文件的數組了。
var fileInput = document.getElementById("myFile");
var files = fileInput.files;
var formData = new FormData();
for(var i = 0; i < files.length; i++) {
var file = files[i];
formData.append('files[]', file, file.name);
}
FormData的append方法提供第三個可選參數用于指定文件名,這樣就可以使用同一個表單項名,然后用文件名區分上傳的多個文件。這樣也方便前后臺的循環操作。
二進制上傳
有了FileReader,其實我們還有一種上傳的途徑,讀取文件內容后直接以二進制格式上傳。
var reader = new FileReader();
reader.onload = function(){
xhr.sendAsBinary(this.result);
}
// 把從input里讀取的文件內容,放到fileReader的result字段里
reader.readAsBinaryString(file);
不過chrome已經把XMLHttpRequest的sendAsBinary方法移除了。所以可能得自行實現一個
XMLHttpRequest.prototype.sendAsBinary = function(text){
var data = new ArrayBuffer(text.length);
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
this.send(ui8a);
}
這段代碼將字符串轉成8位無符號整型,然后存放到一個8位無符號整型數組里面,再把整個數組發送出去。
到這里,我們應該可以結合業務需求實現一個比較優雅的文件上傳組件了。等等,哪里優雅了?都不支持拖拽!
拖拽的支持
利用HTML5的drag & drop事件,我們可以很快實現對拖拽的支持。首先我們可能需要確定一個允許拖放的區域,然后綁定相應的事件進行處理。看代碼
var dropArea;
dropArea = document.getElementById("dropArea");
dropArea.addEventListener("dragenter", handleDragenter, false);
dropArea.addEventListener("dragover", handleDragover, false);
dropArea.addEventListener("drop", handleDrop, false);
// 阻止dragenter和dragover的默認行為,這樣才能使drop事件被觸發function handleDragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function handleDragover(e) {
e.stopPropagation();
e.preventDefault();
}
function handleDrop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
// handle files ...
}
這里可以把通過事件對象的dataTransfer拿到的files數組和之前相同處理,以實現預覽上傳等功能。有了這些事件回調,我們也可以在不同的事件給我們UI元素添加不同的class來實現更好交互效果。
好了,一個比較優雅的上傳組件可以進入生產模式了。什么?還要支持IE9?好吧,讓我們來看看IE10以下的瀏覽器如何實現無刷新上傳。
借用iframe
之前說了要實現文件上傳使用FileUpload對象()即可。這在低版本的IE里也是適用的。那我們為什么還要用iframe呢?
因為在現代瀏覽器中我們可以用XMLHttpRequest Level 2來支持二進制數據,異步文件上傳,并且動態創建FormData。而低版本的IE里的XMLHttpRequest是Level 1。所以我們通過XHR異步向服務器發上傳請求的路走不通了。只能老老實實的用form的submit。
而form的submit會導致頁面的刷新。原因分析好了,那么答案就近在咫尺了。我們能不能讓form的submit不刷新整個頁面呢?答案就是利用iframe。把form的target指定到一個看不見的iframe,那么返回的數據就會被這個iframe接受,于是乎就只有這個iframe會刷新。而它又是看不見的,用戶自然就感知不到了。
window.__iframeCount = 0;
var hiddenframe = document.createElement("iframe");
var frameName = "upload-iframe" + ++window.__iframeCount;
hiddenframe.name = frameName;
hiddenframe.id = frameName;
hiddenframe.setAttribute("style", "width:0;height:0;display:none");
document.body.appendChild(hiddenframe);
var form = document.getElementById("myForm");
form.target = frameName;
然后響應iframe的onload事件,獲取response
hiddenframe.onload = function(){
// 獲取iframe的內容,即服務返回的數據
var resData = this.contentDocument.body.textContent || this.contentWindow.document.body.textContent;
// 處理數據 。。。
//刪除iframe setTimeout(function(){
var _frame = document.getElementById(frameName);
_frame.parentNode.removeChild(_frame);
}, 100);
}
iframe的實現大致如此,但是如果文件上傳的地址與當前頁面不在同一個域下就會出現跨域問題。導致iframe的onload回調里的訪問服務返回的數據失敗。
這時我們再祭出JSONP這把利劍,來解決跨域問題。首先在上傳之前注冊一個全局的函數,把函數名發給服務器。服務器需要配合在response里讓瀏覽器直接調用這個函數。
// 生成全局函數名,避免沖突var CALLBACK_NAME = 'CALLBACK_NAME';
var genCallbackName = (function () {
var i = 0;
return function () {
return CALLBACK_NAME + ++i;
};
})();
var curCallbackName = genCallbackName();
window[curCallbackName] = function(res) {
// 處理response 。。。
// 刪除iframe
var _frame = document.getElementById(frameName);
_frame.parentNode.removeChild(_frame);
// 刪除全局函數本身
window[curCallbackName] = undefined;
}
// 如果已有其他參數,這里需要判斷一下,改為拼接 &callback=
form.action = form.action + '?callback=' + curCallbackName;
好了,實現一個文件上傳組件的基本知識點大致總結了一下。
聯網大文件上傳,網頁大文件上傳,WEB大文件上傳,HTML大文件上傳,HTML5大文件上傳,前端大文件上傳,JavaScript大文件上傳,js大文件上傳,vue大文件上傳,vue2大文件上傳,vue-cli大文件上傳,vue3大文件上傳,webuploader大文件上傳,百度webuploader大文件上傳,互聯網大文件上傳技術,互聯網大文件上傳方案,互聯網大文件上傳解決方案,互聯網大文件上傳斷點續傳,互聯網大文件切片上傳,互聯網大文件切割上傳,
互聯網大文件分片上傳,互聯網大文件分段上傳,互聯網大文件分塊上傳,互聯網大文件秒傳,互聯網大文件上傳校驗,互聯網大文件加密上傳,
用戶上傳的文件比較大,有20G左右,直接用HTML傳的話容易失敗,服務器也容易出錯,需要分片,分塊,分割上傳。也就是將一個大的文件分成若干個小文件塊來上傳,另外就是需要實現秒傳功能和防重復功能,秒傳就是用戶如果上傳過這個文件,那么直接在數據庫中查找記錄就行了,不用再上傳一次,節省時間,實現的思路是對文件做MD5計算,將MD5值保存到數據庫,算法可以用MD5,或者CRC,或者SHA1,這個隨便哪個算法都行。
分片還需要支持斷點續傳,現在HTML5雖然提供了信息記錄功能,但是只支持到了會話級,也就是用戶不能關閉瀏覽器,也不能清空緩存。但是有的政府單位上傳大文件,傳了一半下班了,明天繼續傳,電腦一關結果進度信息就丟失了,這個是他們的一個痛點。
切片的話還有一點就是在服務器上合并,一個文件的所有分片數據上傳完后需要在服務器端進行合并操作。
功能的話支持20G文件上傳和續傳,支持秒傳,支持文件夾上傳,支持在服務端保存文件夾層級結構,支持將文件夾層級結構信息保存到數據庫中,支持下載時能夠將文件夾層級結構下載下來,支持下載文件夾,下載文件夾支持斷點續傳,支持VUE2,VUE3,React,支持IE,Chrome和信創國產化環境,比如銀河麒麟,統信UOS,龍芯,支持加密傳輸,包括加密上傳,加密下載,加密算法支持國密SM4,支持云對象存儲,比如華為云,阿里云,騰訊云,七牛云,AWS,MinIO,FastDFS,需要提供手機,QQ,微信,郵箱等聯系方式,提供7*24小時技術支持,提供長期技術支持和維護服務,提供遠程1對1技術指導,提供二次開發指導,提供文檔教程,提供視頻教程。
下載示例:
https://gitee.com/xproer/up6-jsp-eclipse/tree/6.5.40/
工程
NOSQL
NOSQL示例不需要任何配置,可以直接訪問測試
創建數據表
選擇對應的數據表腳本,這里以SQL為例
修改數據庫連接信息
訪問頁面進行測試
文件存儲路徑
up6/upload/年/月/日/guid/filename
相關問題:
1.javax.servlet.http.HttpServlet錯誤
2.項目無法發布到tomcat
3.md5計算完畢后卡住
4.服務器找不到config.json文件
相關參考:
文件保存位置
源碼工程文檔:https://drive.weixin.qq.com/s?k=ACoAYgezAAw1dWofra
源碼報價單:https://drive.weixin.qq.com/s?k=ACoAYgezAAwoiul8gl
控件源碼下載:https://drive.weixin.qq.com/s?k=ACoAYgezAAwbdKCskc
一般都是在網頁上寫一段javascript腳本,校驗上傳文件的后綴名,有白名單形式也有黑名單形式。
查看源代碼可以看到有如下代碼對上傳文件類型進行了限制:
<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,還可以將html源碼copy一份到本地,然后對相應代碼進行修改,本地提交即可。
即可上傳成功:
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 . '文件夾不存在,請手工創建!';
}
這里做了黑名單處理,我們可以通過特殊可解析后綴進行繞過。
之前在https://www.jianshu.com/p/1ccbab572974中總結過,這里不再贅述,可以使用php3,phtml等繞過。
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.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上傳出錯!';
}
} else {
$msg = '此文件不允許上傳!';
}
} else {
$msg = UPLOAD_PATH . '文件夾不存在,請手工創建!';
}
}
?>
我們發現黑名單限制了很多后綴名,但是沒有限制.htaccess
.htaccess文件是Apache服務器中的一個配置文件,它負責相關目錄下的網頁配置.通過htaccess文件,可以實現:網頁301重定向、自定義404頁面、改變文件擴展名、允許/阻止特定的用戶或者目錄的訪問、禁止目錄列表、配置默認文檔等功能。
我們需要上傳一個.htaccess文件,內容為:
SetHandler application/x-httpd-php
這樣所有的文件都會解析為php,接下來上傳圖片馬即可
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 . '文件夾不存在,請手工創建!';
}
}
我們發現對.htaccess也進行了檢測,但是沒有對大小寫進行統一。
后綴名改為PHP即可
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 . '文件夾不存在,請手工創建!';
}
黑名單沒有對文件中的空格進行處理,可在后綴名中加空格繞過。
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 . '文件夾不存在,請手工創建!';
}
windows會對文件中的點進行自動去除,所以可以在文件末尾加點繞過,不再贅述
同windows特性,可在后綴名中加” ::$DATA”繞過,不再贅述
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 . '文件夾不存在,請手工創建!';
}
}
這里對文件名進行了處理,刪除了文件名末尾的點,并且把處理過的文件名拼接到路徑中。
這里我們可以構造文件名1.PHP. . (點+空格+點),經過處理后,文件名變成1.PHP.,即可繞過。
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 . '文件夾不存在,請手工創建!';
}
}
這里我們可以看到將文件名替換為空,我們可以采用雙寫繞過:1.pphphp
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,我們burp抓包修改即可繞過:
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類型文件!";
}
}
$img_path直接拼接,因此可以利用%00截斷繞過
然后直接訪問/upload/1.php即可
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類型文件!";
}
}
?>
save_path是通過post傳進來的,還是利用00截斷,但這次需要在二進制中進行修改,因為post不會像get對%00進行自動解碼。
接下來訪問1.php即可
主要是檢測文件內容開始處的文件幻數,比如圖片類型的文件幻數如下,
要繞過jpg 文件幻數檢測就要在文件開頭寫上下圖的值:
Value = FF D8 FF E0 00 10 4A 46 49 46
要繞過gif 文件幻數檢測就要在文件開頭寫上下圖的值
Value = 47 49 46 38 39 61
要繞過png 文件幻數檢測就要在文件開頭寫上下面的值
Value = 89 50 4E 47
然后在文件幻數后面加上自己的一句話木馬代碼就行了
圖像文件相關信息檢測常用的就是getimagesize()函數
只需要把文件頭部分偽造好就ok 了,就是在幻數的基礎上還加了一些文件信息
有點像下面的結構
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)
本次環境中的文件頭檢測,getimagesize,php_exif都可以用圖片馬繞過:
copy normal.jpg /b + shell.php /a webshell.jpg
一般是調用API 或函數去進行文件加載測試,常見的是圖像渲染測試,甚至是進行二次渲染(過濾效果幾乎最強)。對渲染/加載測試的攻擊方式是代碼注入繞過,對二次渲染的攻擊方式是攻擊文件加載器自身。
可以用圖像處理軟件對一張圖片進行代碼注入
用winhex 看數據可以分析出這類工具的原理是
在不破壞文件本身的渲染情況下找一個空白區進行填充代碼,一般會是圖片的注釋區
對于渲染測試基本上都能繞過,畢竟本身的文件結構是完整的
imagecreatefromjpeg二次渲染它相當于是把原本屬于圖像數據的部分抓了出來,再用自己的API 或函數進行重新渲染在這個過程中非圖像數據的部分直接就隔離開了
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";
$newimagepath = UPLOAD_PATH.$newfilename;
imagejpeg($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.$newfilename;
@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";
$newimagepath = UPLOAD_PATH.$newfilename;
imagepng($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.$newfilename;
@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";
$newimagepath = UPLOAD_PATH.$newfilename;
imagegif($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path = UPLOAD_PATH.$newfilename;
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上傳出錯!";
}
}else{
$msg = "只允許上傳后綴為.jpg|.png|.gif的圖片文件!";
}
}
本關綜合判斷了后綴名、content-type,以及利用imagecreatefromgif判斷是否為gif圖片,最后再做了一次二次渲染。
得去找圖片經過GD庫轉化后沒有改變的部分,再將未改變的部分修改為相應的php代碼。
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 = '上傳出錯!';
}
}
這里先將文件上傳到服務器,然后通過rename修改名稱,再通過unlink刪除文件,因此可以通過條件競爭的方式在unlink之前,訪問webshell。
然后不斷訪問webshell:
上傳成功。
參考鏈接:
2人點贊
知識歸納
*請認真填寫需求信息,我們會在24小時內與您取得聯系。