阻止任意文件下载漏洞
This commit is contained in:
		
							parent
							
								
									823e95667e
								
							
						
					
					
						commit
						6bb166b89f
					
				@ -5,6 +5,7 @@ import javax.servlet.http.HttpServletResponse;
 | 
				
			|||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					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.GetMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
					import org.springframework.web.bind.annotation.PostMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
@ -41,17 +42,15 @@ public class CommonController
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!FileUtils.isValidFilename(fileName))
 | 
					            if (!FileUtils.checkAllowDownload(fileName))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
 | 
					                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
 | 
					            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
 | 
				
			||||||
            String filePath = RuoYiConfig.getDownloadPath() + fileName;
 | 
					            String filePath = RuoYiConfig.getDownloadPath() + fileName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            response.setCharacterEncoding("utf-8");
 | 
					            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
 | 
				
			||||||
            response.setContentType("multipart/form-data");
 | 
					            FileUtils.setAttachmentResponseHeader(response, realFileName);
 | 
				
			||||||
            response.setHeader("Content-Disposition",
 | 
					 | 
				
			||||||
                    "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName));
 | 
					 | 
				
			||||||
            FileUtils.writeBytes(filePath, response.getOutputStream());
 | 
					            FileUtils.writeBytes(filePath, response.getOutputStream());
 | 
				
			||||||
            if (delete)
 | 
					            if (delete)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -92,18 +91,28 @@ public class CommonController
 | 
				
			|||||||
     * 本地资源通用下载
 | 
					     * 本地资源通用下载
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @GetMapping("/common/download/resource")
 | 
					    @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
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // 本地资源路径
 | 
					        try
 | 
				
			||||||
        String localPath = RuoYiConfig.getProfile();
 | 
					        {
 | 
				
			||||||
        // 数据库资源地址
 | 
					            if (!FileUtils.checkAllowDownload(resource))
 | 
				
			||||||
        String downloadPath = localPath + StringUtils.substringAfter(name, Constants.RESOURCE_PREFIX);
 | 
					            {
 | 
				
			||||||
        // 下载名称
 | 
					                throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
 | 
				
			||||||
        String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
 | 
					            }
 | 
				
			||||||
        response.setCharacterEncoding("utf-8");
 | 
					            // 本地资源路径
 | 
				
			||||||
        response.setContentType("multipart/form-data");
 | 
					            String localPath = RuoYiConfig.getProfile();
 | 
				
			||||||
        response.setHeader("Content-Disposition",
 | 
					            // 数据库资源地址
 | 
				
			||||||
                "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, downloadName));
 | 
					            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
 | 
				
			||||||
        FileUtils.writeBytes(downloadPath, response.getOutputStream());
 | 
					            // 下载名称
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,7 +7,11 @@ import java.io.IOException;
 | 
				
			|||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
import java.io.UnsupportedEncodingException;
 | 
					import java.io.UnsupportedEncodingException;
 | 
				
			||||||
import java.net.URLEncoder;
 | 
					import java.net.URLEncoder;
 | 
				
			||||||
 | 
					import java.nio.charset.StandardCharsets;
 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.ArrayUtils;
 | 
				
			||||||
 | 
					import com.ruoyi.common.utils.StringUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 文件处理工具类
 | 
					 * 文件处理工具类
 | 
				
			||||||
@ -104,6 +108,30 @@ public class FileUtils extends org.apache.commons.io.FileUtils
 | 
				
			|||||||
        return filename.matches(FILENAME_PATTERN);
 | 
					        return filename.matches(FILENAME_PATTERN);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 检查文件是否可下载
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 下载文件名重新编码
 | 
					     * 下载文件名重新编码
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
@ -111,8 +139,7 @@ public class FileUtils extends org.apache.commons.io.FileUtils
 | 
				
			|||||||
     * @param fileName 文件名
 | 
					     * @param fileName 文件名
 | 
				
			||||||
     * @return 编码后的文件名
 | 
					     * @return 编码后的文件名
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
 | 
					    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
 | 
				
			||||||
            throws UnsupportedEncodingException
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        final String agent = request.getHeader("USER-AGENT");
 | 
					        final String agent = request.getHeader("USER-AGENT");
 | 
				
			||||||
        String filename = fileName;
 | 
					        String filename = fileName;
 | 
				
			||||||
@ -139,4 +166,38 @@ public class FileUtils extends org.apache.commons.io.FileUtils
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return filename;
 | 
					        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");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user