RuoYi
2020-11-17 deb3594b40146777b749cc3b420ed632ae13959c
阻止任意文件下载漏洞
已修改2个文件
已添加1个文件
155 ■■■■ 文件已修改
src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/file/FileUtils.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/common/CommonController.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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();
    }
}
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 æ­£å¸¸ 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");
    }
}
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);
        }
    }
}