01
檔案上傳漏洞原理
在檔案上傳的功能處,若服務端指令碼語言未對上傳的檔案進行嚴格驗證和過濾,導致惡意使用者上傳惡意的指令碼檔案時,就有可能獲取執行服務端命令的能力,這就是檔案上傳漏洞。
02
檔案上傳漏洞觸發點
相簿、頭像上傳、影片、照片分享、附件上傳(論壇發帖、郵箱)、檔案管理器等。
03
檔案上傳漏洞的形成條件
檔案能夠透過前端和後端過濾和檔案處理
檔案內容不會改變,能夠被正確儲存
儲存位置在Web容器控制範圍內
攻擊者有許可權訪問儲存目錄並有權執行檔案
只要破壞了其中的任一條件即可防止檔案上傳漏洞。
04
檔案上傳漏洞常見防禦方法
0.無防護
以下程式碼實現了一個簡單的檔案上傳功能
index.html
1<form action="/upload" method="post" enctype="multipart/form-data">
2 <input type="file" name="uploadfile" >
3 <input type="submit">
4</form>
上傳檔案的後端處理,基於Spring Boot
1package com.example.fileuploadvul.Controller;
2
3import org.springframework.stereotype.Controller;
4import org.springframework.web.bind.annotation.PostMapping;
5import org.springframework.web.bind.annotation.RequestParam;
6import org.springframework.web.multipart.MultipartFile;
7
8import java.io.File;
9import java.io.IOException;
10
11@Controller
12public class UploadFile {
13 @PostMapping("/upload")
14 public String uploadFile(@RequestParam("uploadfile")MultipartFile file){
15//獲取檔名
16 String filename = file.getOriginalFilename();
17 //檔案儲存路徑
18 String path="F:\tmp\";
19 File outfile = new File(path + filename);
20 try {
21 file.transferTo(outfile);
22 }catch (IOException e){
23 e.printStackTrace();
24 }
25 return "success";
26 }
27}
上面的程式碼沒有任何的防護功能,存在檔案上傳漏洞。使用者可以隨意的上傳任何檔案、木馬。
1.前端進行js校驗
增加攻擊成本,該種方式容易被繞過。
javascript示例:
1<form action="/upload" method="post" onsubmit="return judge()" enctype="multipart/form-data">
2 <input type="file" name="uploadfile" id="checkfile" >
3 <input type="submit" value="提交">
4 <p id="msg"></p>
5</form>
6<script type="text/javascript">
7
8 function judge(){
9 var file=document.getElementById("checkfile").value;
10 if (file==null||file==""){
11 alert("請選擇要上傳的檔案");
12 // location.reload(true);
13 return false;
14 }
15 var isnext=false;
16 //定義允許上傳的檔案型別
17 var filetypes=[".jpg",".png"];
18 //提取上傳檔案的型別,其中這裡需要注意用lastIndexOf而非indexOf,否則會被1.php.php繞過
19 var fileend=file.substring(file.lastIndexOf("."));
20 //判斷上傳檔案型別是否允許上傳寫法一
21 for (var i=0;i<filetypes.length;i++){
22 if (filetypes[i]==fileend){
23 isnext=true;
24 break;
25 }
26 }
27 if (!isnext){
28 document.getElementById("msg").innerHTML="檔案型別不允許";
29 // location.reload(true);
30 return false;
31 }else {
32 return true;
33 }
34 //判斷上傳檔案型別是否允許上傳寫法二
35 if (fileend.indexOf(filetypes) == -1) {
36 var errMsg = "該檔案不允許上傳";
37 alert(errMsg);
38 return false;
39 }
40 }
41</script>
繞過方法:
這裡限制了只能上傳.jpg .png檔案,但是攻擊者可以用burpsuite攔截修改包進行繞過。
如圖,上傳一個內容為php的jpg檔案,然後在此處將filename修改為1.php,即可繞過前端js驗證。
另外也可以利用外掛禁用js後進行提交。
2.白名單/黑名單
2.1黑名單
2.1.1java示例:
1@Controller
2public class UploadFile {
3 @PostMapping("/upload")
4 public String uploadFile(@RequestParam("uploadfile")MultipartFile file, Model model){
5 boolean flag=true;
6 String filename = file.getOriginalFilename();
7 System.out.println(filename);
8 String suffix=filename.substring(filename.lastIndexOf("."));
9 String[] blacklist={".jsp",".php",".exe",".dll","vxd","html"};//字尾名黑名單
10 for (String s : blacklist) {
11 if (suffix.equals(s)){
12 flag=false;
13 break;
14 }
15 }
16 if (flag){
17 String path="src\main\resources\static\upload";
18 File fileDir = new File(path);
19 File outfile = new File(fileDir.getAbsolutePath()+File.separator + filename);
20 try {
21 file.transferTo(outfile);
22 return "success";
23 }catch (IOException e){
24 e.printStackTrace();
25 }
26 }
27 else {
28 model.addAttribute("msg","非法檔案型別");
29 }
30 return "index";
31 }
32}
2.1.2php示例:
1$is_upload = false;
2$msg = null;
3if (isset($_POST['submit'])) {
4 if (file_exists(UPLOAD_PATH)) {
5 $deny_ext = array('.asp','.aspx','.php','.jsp');
6 $file_name = trim($_FILES['upload_file']['name']);
7 $file_name = deldot($file_name);//刪除檔名末尾的點
8 $file_ext = strrchr($file_name, '.');
9 $file_ext = strtolower($file_ext); //轉換為小寫
10 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字串::$DATA
11 $file_ext = trim($file_ext); //收尾去空
12
13 if(!in_array($file_ext, $deny_ext)) {
14 $temp_file = $_FILES['upload_file']['tmp_name'];
15 $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
16 if (move_uploaded_file($temp_file,$img_path)) {
17 $is_upload = true;
18 } else {
19 $msg = '上傳出錯!';
20 }
21 } else {
22 $msg = '不允許上傳.asp,.aspx,.php,.jsp字尾檔案!';
23 }
24 } else {
25 $msg = UPLOAD_PATH . '資料夾不存在,請手工建立!';
26 }
27}
《駭客&網路安全入門&進階學習資源包》分享 (qq.com)
2.1.3繞過方法:
如圖成功上傳了phps檔案。
黑名單容易出現大小寫(pHp)、特殊可解析字尾(pht)、.htaccess、點、空格、::DATA、雙寫等繞過問題。
另外可能使用Intruder模組進行列舉字尾名,如使用字典
另外,“判斷檔案字尾名是否存在黑名單中的字元,將對應的字串替換為空”這種方式也是不可取的。
String[] blacklist={"jsp","php","exe","dll","vxd","html"};//字尾名黑名單
1String[] blacklist={"jsp","php","exe","dll","vxd","html"};//字尾名黑名單
2for (String s : blacklist) {
3 if (suffix.indexOf(s)!=-1){
4 suffix=suffix.replace(s,"");//字尾存在黑名單字串,則將字串替換為空
5 }
6}
這種方式可以透過雙寫 phphpp 進行繞過。
2.2白名單
2.2.1java示例:
1String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
2String[] white_suffix = {"gif","jpg","jpeg","png"};
3Boolean fsFlag = false;
4for (String suffix:white_suffix){
5 if (contentType.equalsIgnoreCase(fileSuffix)){
6 fsFlag = true;
7 break;
8 }
9}
10if (!fsFlag){
11 return "suffix not allow";
12}
2.2.2php示例:
1$is_upload = false;
2$msg = null;
3if(isset($_POST['submit'])){
4 $ext_arr = array('jpg','png','gif');
5 $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
6 if(in_array($file_ext,$ext_arr)){
7 $temp_file = $_FILES['upload_file']['tmp_name'];
8 $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
9
10 if(move_uploaded_file($temp_file,$img_path)){
11 $is_upload = true;
12 } else {
13 $msg = '上傳出錯!';
14 }
15 } else{
16 $msg = "只允許上傳.jpg|.png|.gif型別檔案!";
17 }
18}
對字尾進行白名單限制不易被繞過。
因此建議使用白名單而非黑名單。
3.檢查檔案型別MIME Type
3.1java示例
1//1、MIME檢測
2 String contentType = file.getContentType();
3 String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"};
4 Boolean ctFlag = false;
5 for (String suffix:white_type){
6 if (contentType.equalsIgnoreCase(suffix)){
7 ctFlag = true;
8 break;
9 }
10 }
11 if (!ctFlag){
12 return "content-type not allow";
13 }
3.2php示例
1if (($_FILES['upload_file']['type'] == 'image/jpeg') ||
2($_FILES['upload_file']['type'] == 'image/png') ||
3($_FILES['upload_file']['type'] == 'image/gif'))
增加攻擊成本,該種方式容易被繞過。
3.3繞過方法:
如圖可以直接修改包。
4.使用隨機數改寫檔名
4.1php示例
1if(in_array($file_ext,$ext_arr)){
2 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
3 rename($upload_file, $img_path);
4 $is_upload = true;
4.2java示例
1String uuid = UUID.randomUUID().toString();
2fileName = uuid+fileName.substring(fileName.lastIndexOf("."));
能夠增加攻擊成本,另外可以防範某些特殊名稱檔案(shell.php.rar.rar)
5.對檔案內容進行校驗
5.1對檔案頭進行校驗
1.jpg FF D8 FF E0 00 10 4A 46 49 46
2.gif 47 49 46 38 39 61
3.png 89 50 4E 47
以圖片為例,可以擷取檔案的頭部幾個位元組進行校驗,如使用getimagesize(不建議使用)
增加攻擊成本,易被繞過,攻擊者同樣可以在檔案的頭部增加如下位元組:
5.2對檔案內容進行檢測
繞過<?:
1<script language='php'>@eval($_POST[cmd]);</script>
繞過php:
1<?= @eval($_POST['cmd']);?>
同時繞過:
1<script language='pHp'>@eval($_POST[cmd]);</script>
6.對檔案的處理邏輯。
若先將檔案放入路徑,再去檢驗檔案合法性並刪除,會存在條件競爭漏洞。
6.1示例:
1if(isset($_POST['submit'])){
2 $ext_arr = array('jpg','png','gif');
3 $file_name = $_FILES['upload_file']['name'];
4 $temp_file = $_FILES['upload_file']['tmp_name'];
5 $file_ext = substr($file_name,strrpos($file_name,".")+1);
6 $upload_file = UPLOAD_PATH . '/' . $file_name;
7
8 if(move_uploaded_file($temp_file, $upload_file)){
9 if(in_array($file_ext,$ext_arr)){
10 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
11 rename($upload_file, $img_path);
12 $is_upload = true;
13 }else{
14 $msg = "只允許上傳.jpg|.png|.gif型別檔案!";
15 unlink($upload_file);
16 }
17 }else{
18 $msg = '上傳出錯!';
19 }
20}
先用move_uploaded_file 將上傳的檔案移動到上傳目錄,再判斷字尾名並就刪除檔案。
我們多執行緒非同步上傳檔案A並不斷地訪問自己上傳的檔案A,可以檔案A進行刪除之前,成功執行檔案A,
若檔案A程式碼為:
1');?>
則執行成功可以生成穩定的shell.php
解決方案:經過充分完整的檢查之後再上傳。
7.禁止上傳檔案被執行
7.1儲存伺服器
將儲存上傳檔案的位置設計在另一臺只具備儲存功能的檔案伺服器或資料庫上,與Web應用伺服器分開,這樣即使木馬被上傳進來,也因為檔案伺服器不能執行指令碼而沒有辦法實施攻擊。
7.2設定許可權
改儲存上傳檔案的目錄的執行指令碼的許可權。如linux系統下使用chmod命令修改目錄的rwx許可權。
7.3修改配置
修改.htaccess、apache的httpd.conf配置檔案、Nginx增加配置
7.3.1 .htaccess
1<FilesMatch ".(?i:php|php3|php4|php5)">
2Order allow,deny
3Deny from all
4</FilesMatch>
7.3.2 修改apache的配置檔案httpd.conf
1<Directory D:\wwwroot\public\uploads>
2<FilesMatch ".(?i:php|php3|php4|php5)">
3 Order allow,deny
4 Deny from all
5</FilesMatch>
6</Directory>
7.4 隱藏上傳檔案路徑
示例:使用blob把網站上的全部圖片連結加密。
後臺返回圖片:
1protected void Page_Load(object sender, EventArgs e)
2 {
3 string url = Server.MapPath("~/Images/abg.jpg");
4 Bitmap b = new Bitmap(url);
5 MemoryStream ms = new MemoryStream();
6 b.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
7 Response.ClearContent();
8 Response.ContentType = "image/Jpg";
9 Response.BinaryWrite(ms.ToArray());
10 }
JavaScript程式碼:
1 <img id="img1" src="">
2 <script type="text/javascript">
3 //建立XMLHttpRequest物件
4 var xhr = new XMLHttpRequest();
5 //配置請求方式、請求地址以及是否同步
6 xhr.open('POST', '/Default2.aspx', true);
7 //設定請求結果型別為blob
8 xhr.responseType = 'blob';
9 //請求成功回撥函式
10 xhr.onload = function(e) {
11 if (this.status == 200) {//請求成功
12 //獲取blob物件
13 var blob = this.response;
14 //獲取blob物件地址,並把值賦給容器
15 $("#img1").attr("src", URL.createObjectURL(blob));
16 }
17 };
18 xhr.send();
19 </script>
回到網頁檢視img標籤src的地址:
8.檔案二次渲染
1$is_upload = false;
2$msg = null;
3if (isset($_POST['submit'])){
4 // 獲得上傳檔案的基本資訊,檔名,型別,大小,臨時檔案路徑
5 $filename = $_FILES['upload_file']['name'];
6 $filetype = $_FILES['upload_file']['type'];
7 $tmpname = $_FILES['upload_file']['tmp_name'];
8
9 $target_path=UPLOAD_PATH.basename($filename);
10
11 // 獲得上傳檔案的副檔名
12 $fileext= substr(strrchr($filename,"."),1);
13
14 //判斷檔案字尾與型別,合法才進行上傳操作
15 if(($fileext == "jpg") && ($filetype=="image/jpeg")){
16 if(move_uploaded_file($tmpname,$target_path))
17 {
18 //使用上傳的圖片生成新的圖片
19 $im = imagecreatefromjpeg($target_path);
20
21 if($im == false){
22 $msg = "該檔案不是jpg格式的圖片!";
23 @unlink($target_path);
24 }else{
25 //給新圖片指定檔名
26 srand(time());
27 $newfilename = strval(rand()).".jpg";
28 $newimagepath = UPLOAD_PATH.$newfilename;
29 imagejpeg($im,$newimagepath);
30 //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
31 $img_path = UPLOAD_PATH.$newfilename;
32 @unlink($target_path);
33 $is_upload = true;
34 }
35 } else {
36 $msg = "上傳出錯!";
37 }
38
39 }else if(($fileext == "png") && ($filetype=="image/png")){
40 if(move_uploaded_file($tmpname,$target_path))
41 {
42 //使用上傳的圖片生成新的圖片
43 $im = imagecreatefrompng($target_path);
44
45 if($im == false){
46 $msg = "該檔案不是png格式的圖片!";
47 @unlink($target_path);
48 }else{
49 //給新圖片指定檔名
50 srand(time());
51 $newfilename = strval(rand()).".png";
52 $newimagepath = UPLOAD_PATH.$newfilename;
53 imagepng($im,$newimagepath);
54 //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
55 $img_path = UPLOAD_PATH.$newfilename;
56 @unlink($target_path);
57 $is_upload = true;
58 }
59 } else {
60 $msg = "上傳出錯!";
61 }
62
63 }else if(($fileext == "gif") && ($filetype=="image/gif")){
64 if(move_uploaded_file($tmpname,$target_path))
65 {
66 //使用上傳的圖片生成新的圖片
67 $im = imagecreatefromgif($target_path);
68 if($im == false){
69 $msg = "該檔案不是gif格式的圖片!";
70 @unlink($target_path);
71 }else{
72 //給新圖片指定檔名
73 srand(time());
74 $newfilename = strval(rand()).".gif";
75 $newimagepath = UPLOAD_PATH.$newfilename;
76 imagegif($im,$newimagepath);
77 //顯示二次渲染後的圖片(使用使用者上傳圖片生成的新圖片)
78 $img_path = UPLOAD_PATH.$newfilename;
79 @unlink($target_path);
80 $is_upload = true;
81 }
82 } else {
83 $msg = "上傳出錯!";
84 }
85 }else{
86 $msg = "只允許上傳字尾為.jpg|.png|.gif的圖片檔案!";
87 }
88}
繞過方法:
對比上傳前後的兩個檔案,找到沒有變動的區域,插入php程式碼。
《駭客&網路安全入門&進階學習資源包》分享 (qq.com)
參考文章:
1https://wiki.wgpsec.org/knowledge/ctf/uploadfile.html
2
3https://blog.csdn.net/cldimd/article/details/104992488
4
5https://github.com/c0ny1/upload-labs
6
7https://juejin.cn/post/6989580413333159949
8
9https://blog.csdn.net/liguangyao213/article/details/123430199
10
11https://blog.csdn.net/qq_43277152/article/details/113373721
12
13https://blog.csdn.net/qq_45570082/article/details/108910162
14
15https://xz.aliyun.com/t/3941
16
17https://blog.csdn.net/weixin_64551911/article/details/124627363?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&utm_relevant_index=5
18
19等。