From deb3594b40146777b749cc3b420ed632ae13959c Mon Sep 17 00:00:00 2001
From: RuoYi <yzz_ivy@163.com>
Date: 星期二, 17 十一月 2020 10:21:19 +0800
Subject: [PATCH] 阻止任意文件下载漏洞

---
 src/main/java/com/ruoyi/common/utils/file/FileUtils.java     |   65 +++++++++++++++++++++
 src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java |   47 +++++++++++++++
 src/main/java/com/ruoyi/project/common/CommonController.java |   43 ++++++++-----
 3 files changed, 136 insertions(+), 19 deletions(-)

diff --git a/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java
new file mode 100644
index 0000000..65be65b
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java
@@ -0,0 +1,47 @@
+package com.ruoyi.common.utils.file;
+
+import java.io.File;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 鏂囦欢绫诲瀷宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class FileTypeUtils
+{
+    /**
+     * 鑾峰彇鏂囦欢绫诲瀷
+     * <p>
+     * 渚嬪: ruoyi.txt, 杩斿洖: txt
+     * 
+     * @param file 鏂囦欢鍚�
+     * @return 鍚庣紑锛堜笉鍚�".")
+     */
+    public static String getFileType(File file)
+    {
+        if (null == file)
+        {
+            return StringUtils.EMPTY;
+        }
+        return getFileType(file.getName());
+    }
+
+    /**
+     * 鑾峰彇鏂囦欢绫诲瀷
+     * <p>
+     * 渚嬪: ruoyi.txt, 杩斿洖: txt
+     *
+     * @param fileName 鏂囦欢鍚�
+     * @return 鍚庣紑锛堜笉鍚�".")
+     */
+    public static String getFileType(String fileName)
+    {
+        int separatorIndex = fileName.lastIndexOf(".");
+        if (separatorIndex < 0)
+        {
+            return "";
+        }
+        return fileName.substring(separatorIndex + 1).toLowerCase();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
index f0dee60..6bc2ce6 100644
--- a/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
+++ b/src/main/java/com/ruoyi/common/utils/file/FileUtils.java
@@ -7,7 +7,11 @@
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import com.ruoyi.common.utils.StringUtils;
 
 /**
  * 鏂囦欢澶勭悊宸ュ叿绫�
@@ -105,14 +109,37 @@
     }
 
     /**
+     * 妫�鏌ユ枃浠舵槸鍚﹀彲涓嬭浇
+     * 
+     * @param resource 闇�瑕佷笅杞界殑鏂囦欢
+     * @return true 姝e父 false 闈炴硶
+     */
+    public static boolean checkAllowDownload(String resource)
+    {
+        // 绂佹鐩綍涓婅烦绾у埆
+        if (StringUtils.contains(resource, ".."))
+        {
+            return false;
+        }
+
+        // 妫�鏌ュ厑璁镐笅杞界殑鏂囦欢瑙勫垯
+        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        {
+            return true;
+        }
+
+        // 涓嶅湪鍏佽涓嬭浇鐨勬枃浠惰鍒�
+        return false;
+    }
+
+    /**
      * 涓嬭浇鏂囦欢鍚嶉噸鏂扮紪鐮�
      * 
      * @param request 璇锋眰瀵硅薄
      * @param fileName 鏂囦欢鍚�
      * @return 缂栫爜鍚庣殑鏂囦欢鍚�
      */
-    public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
-            throws UnsupportedEncodingException
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
     {
         final String agent = request.getHeader("USER-AGENT");
         String filename = fileName;
@@ -139,4 +166,38 @@
         }
         return filename;
     }
+
+    /**
+     * 涓嬭浇鏂囦欢鍚嶉噸鏂扮紪鐮�
+     *
+     * @param response 鍝嶅簲瀵硅薄
+     * @param realFileName 鐪熷疄鏂囦欢鍚�
+     * @return
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+    {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        StringBuilder contentDispositionValue = new StringBuilder();
+        contentDispositionValue.append("attachment; filename=")
+                .append(percentEncodedFileName)
+                .append(";")
+                .append("filename*=")
+                .append("utf-8''")
+                .append(percentEncodedFileName);
+
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+    }
+
+    /**
+     * 鐧惧垎鍙风紪鐮佸伐鍏锋柟娉�
+     *
+     * @param s 闇�瑕佺櫨鍒嗗彿缂栫爜鐨勫瓧绗︿覆
+     * @return 鐧惧垎鍙风紪鐮佸悗鐨勫瓧绗︿覆
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException
+    {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
 }
diff --git a/src/main/java/com/ruoyi/project/common/CommonController.java b/src/main/java/com/ruoyi/project/common/CommonController.java
index b59fb29..4a73496 100644
--- a/src/main/java/com/ruoyi/project/common/CommonController.java
+++ b/src/main/java/com/ruoyi/project/common/CommonController.java
@@ -5,6 +5,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -41,17 +42,15 @@
     {
         try
         {
-            if (!FileUtils.isValidFilename(fileName))
+            if (!FileUtils.checkAllowDownload(fileName))
             {
                 throw new Exception(StringUtils.format("鏂囦欢鍚嶇О({})闈炴硶锛屼笉鍏佽涓嬭浇銆� ", fileName));
             }
             String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
             String filePath = RuoYiConfig.getDownloadPath() + fileName;
 
-            response.setCharacterEncoding("utf-8");
-            response.setContentType("multipart/form-data");
-            response.setHeader("Content-Disposition",
-                    "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName));
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, realFileName);
             FileUtils.writeBytes(filePath, response.getOutputStream());
             if (delete)
             {
@@ -92,18 +91,28 @@
      * 鏈湴璧勬簮閫氱敤涓嬭浇
      */
     @GetMapping("/common/download/resource")
-    public void resourceDownload(String name, HttpServletRequest request, HttpServletResponse response) throws Exception
+    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
+            throws Exception
     {
-        // 鏈湴璧勬簮璺緞
-        String localPath = RuoYiConfig.getProfile();
-        // 鏁版嵁搴撹祫婧愬湴鍧�
-        String downloadPath = localPath + StringUtils.substringAfter(name, Constants.RESOURCE_PREFIX);
-        // 涓嬭浇鍚嶇О
-        String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
-        response.setCharacterEncoding("utf-8");
-        response.setContentType("multipart/form-data");
-        response.setHeader("Content-Disposition",
-                "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName));
-        FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        try
+        {
+            if (!FileUtils.checkAllowDownload(resource))
+            {
+                throw new Exception(StringUtils.format("璧勬簮鏂囦欢({})闈炴硶锛屼笉鍏佽涓嬭浇銆� ", resource));
+            }
+            // 鏈湴璧勬簮璺緞
+            String localPath = RuoYiConfig.getProfile();
+            // 鏁版嵁搴撹祫婧愬湴鍧�
+            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
+            // 涓嬭浇鍚嶇О
+            String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
+            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+            FileUtils.setAttachmentResponseHeader(response, downloadName);
+            FileUtils.writeBytes(downloadPath, response.getOutputStream());
+        }
+        catch (Exception e)
+        {
+            log.error("涓嬭浇鏂囦欢澶辫触", e);
+        }
     }
 }

--
Gitblit v1.9.3