java文件上传漏洞修复工具类

一、前言

最近安全人员测试系统,说文件上传接口有安全漏洞;

因为文件流是前端传给后台的,说是如果不校验格式的话、会有各种问题。

再此总结下工具类方法。

二、代码

1.常量类

import java.util.Arrays;
import java.util.List;

public class FileTypeContants {
	public static final String FILE_OFFICE = "office";//Word、Excel、PPT
	public static final String FILE_IMG = "img";//图片
	public static final String FILE_AUDIO = "audio";//音频
	public static final String FILE_VEDIO = "vedio";//视频
	public static final String FILE_PDF = "pdf";//pdf
	
	public static final String FILE_SUFFIX_ERROR	= "文件后缀不正确或文件不存在";
	public static final String FILE_TYPE_ERROR	= "文件类型不正确";
	public static final String FILE_HEADER_ERROR	= "文件头不正确";
	public static final String FILE_SIZE_ERROR	= "文件过大";
	
	//Word、Excel、PPT 文件后缀
	public static final List<String> FILE_SUFFIX_OFFICE = Arrays.asList("doc","docx", "xls","xlsx","ppt","pptx");
	//图片 文件后缀
    public static final List<String> FILE_SUFFIX_IMG = Arrays.asList("png","jpeg","jpg");
    //音频 文件后缀(目前视频只支持MP3)
    public static final List<String> FILE_SUFFIX_AUDIO = Arrays.asList("mp3");
    //视频 文件后缀(目前视频只支持MP4)
    public static final List<String> FILE_SUFFIX_VEDIO = Arrays.asList("mp4");
    //PDF 文件后缀
    public static final List<String> FILE_SUFFIX_PDF = Arrays.asList("pdf");
    
    
    //Word、Excel、PPT 文件类型
    public static final List<String> FILE_TYPE_OFFICE = Arrays.asList(
    		"application/msword",//doc
    		"application/vnd.openxmlformats-officedocument.wordprocessingml.document",//docx
    		"application/vnd.ms-excel",//xls
    		"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",//xlsx
    		"application/vnd.ms-powerpoint",//ppt
    		"application/vnd.openxmlformats-officedocument.presentationml.presentation"//pptx
    		);
    //图片 文件类型
    public static final List<String> FILE_TYPE_IMG = Arrays.asList("image/png","image/jpeg");//jpeg jpg 是一样的
    //音频 文件类型
    public static final List<String> FILE_TYPE_AUDIO = Arrays.asList("audio/mpeg");  
    //视频 文件类型
    public static final List<String> FILE_TYPE_VEDIO = Arrays.asList("video/mp4"); 
    //pdf 文件类型
    public static final List<String> FILE_TYPE_PDF = Arrays.asList("application/pdf");
    
    
    //不同类型文件的文件头判断长度不一样
    //Word、Excel、PPT 文件头
    public static final String FILE_HEADER_OFFICE = "D0CF11E0";//doc、xls、ppt
    public static final String FILE_HEADER_OFFICEX = "504B0304";//docx、xlsx、pptx
    //图片 文件头
    public static final String FILE_HEADER_PNG = "89504E47";
    public static final String FILE_HEADER_JPG = "FFD8FF";//jpeg jpg 是一样的
    //音频 文件头
    public static final String FILE_HEADER_MP3 = "494433";
    /**
     * 	MP4 文件头有很多种,先只取 000000
     *  00 00 00 14 66 74 79 70 69 73 6f 6d
		00 00 00 18 66 74 79 70
		00 00 00 1c 66 74 79 70
		00 00 00 20 66 74 79 70 4d 34 41
     */
    public static final String FILE_HEADER_MP4 = "000000";
    //pdf 文件头
    public static final String FILE_HEADER_PDF = "25504446";
    
    
    
    //Word、Excel、PPT 文件大小
    public static final long FILE_MAXSIZE_OFFICE = 30 * 1024 ;	// Word/Excel/PPT 都限制30M
    //图片 大小
    public static final long FILE_MAXSIZE_IMG = 10 * 1024;//图片限制10M
    //音频 文件大小
    public static final long FILE_MAXSIZE_AUDIO = 30 * 1024;//MP3限制30M
    //视频 文件大小
    public static final long FILE_MAXSIZE_VEDIO = 2 * 1024 * 1024;//MP4限制2G
    //pdf 文件大小
    public static final long FILE_MAXSIZE_PDF = 20 * 1024;//PDF限制10M
    
   
    //其他备用
    public static final String FILE_TYPE_JSON = "application/json";
    public static final String FILE_TYPE_XML = "application/xml";
    public static final String FILE_TYPE_SVG = "image/svg";  
}

说明:
这个是常量类,后续util工具类会用这些常量。

2.工具类

import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@Component
public class FileTypeUtil {
    
    /**
     * 判断文件是否符合要求
     * 文件后缀、文件类型、文件头、文件大小
     * @param file
     * @param fileType
     * @return
     */
    public static String checkSafeMultipartFile(MultipartFile file, String...fileTypes){
    	
    	if(file != null && fileTypes != null && fileTypes.length > 0){
            //文件后缀
            String fileName = file.getOriginalFilename();
            Integer index = fileName.lastIndexOf(".");
            String suffix = fileName.substring(index+1).toLowerCase();
            
            //文件大小
            long fileSize = file.getSize()/1024;
            
            //文件类型
            String fileContentType = file.getContentType();
            
            //文件头
            String fileHeader = FileTypeUtil.getFileHeader(file);
            
        	for(String fileType : fileTypes){
        		if(FileTypeContants.FILE_OFFICE.equals(fileType) && FileTypeContants.FILE_SUFFIX_OFFICE.contains(suffix)){
        			//Word、Excel、PPT
        			return FileTypeUtil.checkOffice(fileSize,fileContentType,fileHeader);
        		}else if(FileTypeContants.FILE_IMG.equals(fileType) && FileTypeContants.FILE_SUFFIX_IMG.contains(suffix)){
        			//图片
        			return FileTypeUtil.checkImg(fileSize,fileContentType,fileHeader);
        		}else if(FileTypeContants.FILE_AUDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_AUDIO.contains(suffix)){
        			//音频
        			return FileTypeUtil.checkAudio(fileSize,fileContentType,fileHeader);
        		}else if(FileTypeContants.FILE_VEDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_VEDIO.contains(suffix)){
        			//视频
        			return FileTypeUtil.checkVedio(fileSize,fileContentType,fileHeader);
        		}else if(FileTypeContants.FILE_PDF.equals(fileType) && FileTypeContants.FILE_SUFFIX_PDF.contains(suffix)){
        			//PDF
        			return FileTypeUtil.checkPDF(fileSize,fileContentType,fileHeader);
        		}
        	}
    	}
    	return FileTypeContants.FILE_SUFFIX_ERROR;
    }

	public static String checkSafeNormalFile(File file, String...fileTypes){

		if(file != null && fileTypes != null && file.exists() && fileTypes.length > 0){
			//文件后缀
			String fileName = file.getName();
			Integer index = fileName.lastIndexOf(".");
			String suffix = fileName.substring(index+1).toLowerCase();

			//文件大小
			long fileSize = file.length()/1024;

			//文件类型,普通文件没有这个
			//String fileContentType = file.getContentType();

			//文件头
			String fileHeader = FileTypeUtil.getNormalFileHeader(file);

			for(String fileType : fileTypes){
				if(FileTypeContants.FILE_OFFICE.equals(fileType) && FileTypeContants.FILE_SUFFIX_OFFICE.contains(suffix)){
					//Word、Excel、PPT
					return FileTypeUtil.checkOffice(fileSize, fileHeader);
				}else if(FileTypeContants.FILE_IMG.equals(fileType) && FileTypeContants.FILE_SUFFIX_IMG.contains(suffix)){
					//图片
					return FileTypeUtil.checkImg(fileSize, fileHeader);
				}else if(FileTypeContants.FILE_AUDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_AUDIO.contains(suffix)){
					//音频
					return FileTypeUtil.checkAudio(fileSize, fileHeader);
				}else if(FileTypeContants.FILE_VEDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_VEDIO.contains(suffix)){
					//视频
					return FileTypeUtil.checkVedio(fileSize, fileHeader);
				}else if(FileTypeContants.FILE_PDF.equals(fileType) && FileTypeContants.FILE_SUFFIX_PDF.contains(suffix)){
					//PDF
					return FileTypeUtil.checkPDF(fileSize, fileHeader);
				}
			}
		}
		return FileTypeContants.FILE_SUFFIX_ERROR;
	}
    
    /**
     * 判断文件是否符合要求,多个文件
     * 文件后缀、文件类型、文件头、文件大小
     * @param files
     * @param fileTypes
     * @return
     */
    public static String checkSafeMultipartFiles(MultipartFile[] files, String...fileTypes){
    	String checkMessage = "";
    	
    	for(MultipartFile file : files){
    		checkMessage = FileTypeUtil.checkSafeMultipartFile(file, fileTypes);
    		if(!"".equals(checkMessage)){
    			return checkMessage;
    		}
    	}
    	
    	return checkMessage;
    }
    
    /**
     * 判断 Word、Excel、PPT
     * @param fileSize
     * @param fileContentType
     * @param fileHeader
     * @return
     */
    public static String checkOffice(long fileSize,String fileContentType ,String fileHeader){
    	//判断文件类型
    	if(!FileTypeContants.FILE_TYPE_OFFICE.contains(fileContentType)){
			return FileTypeContants.FILE_TYPE_ERROR;
		}
    	
    	//判断文件头
    	if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICE) 
    			&& !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICEX)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

    	//判断文件大小
    	if(fileSize > FileTypeContants.FILE_MAXSIZE_OFFICE){
    		return FileTypeContants.FILE_SIZE_ERROR;
		}
    	
    	return "";
    }

	public static String checkOffice(long fileSize, String fileHeader){

		//判断文件头
		if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICE)
				&& !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICEX)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

		//判断文件大小
		if(fileSize > FileTypeContants.FILE_MAXSIZE_OFFICE){
			return FileTypeContants.FILE_SIZE_ERROR;
		}

		return "";
	}
    
    /**
     * 判断图片
     * @param fileSize
     * @param fileContentType
     * @param fileHeader
     * @return
     */
    public static String checkImg(long fileSize, String fileContentType, String fileHeader){
    	//判断文件类型    	
    	if(!FileTypeContants.FILE_TYPE_IMG.contains(fileContentType)){
			return FileTypeContants.FILE_TYPE_ERROR;
		}

    	//判断文件头
    	if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PNG) 
    			&& !fileHeader.startsWith(FileTypeContants.FILE_HEADER_JPG)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

    	//判断文件大小
    	if(fileSize > FileTypeContants.FILE_MAXSIZE_IMG){
    		return FileTypeContants.FILE_SIZE_ERROR;
		}
    	
    	return "";
    }

	public static String checkImg(long fileSize, String fileHeader){

		//判断文件头
		if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PNG)
				&& !fileHeader.startsWith(FileTypeContants.FILE_HEADER_JPG)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

		//判断文件大小
		if(fileSize > FileTypeContants.FILE_MAXSIZE_IMG){
			return FileTypeContants.FILE_SIZE_ERROR;
		}

		return "";
	}
    

    /**
     * 判断音频
     * @param fileSize
     * @param fileContentType
     * @param fileHeader
     * @return
     */
    public static String checkAudio(long fileSize, String fileContentType, String fileHeader){
    	//判断文件类型
    	if(!FileTypeContants.FILE_TYPE_AUDIO.contains(fileContentType)){
			return FileTypeContants.FILE_TYPE_ERROR;
		}

    	//判断文件头
    	if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP3)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

    	//判断文件大小
    	if(fileSize > FileTypeContants.FILE_MAXSIZE_AUDIO){
    		return FileTypeContants.FILE_SIZE_ERROR;
		}
    	
    	return "";
    }

	public static String checkAudio(long fileSize, String fileHeader){

		//判断文件头
		if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP3)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

		//判断文件大小
		if(fileSize > FileTypeContants.FILE_MAXSIZE_AUDIO){
			return FileTypeContants.FILE_SIZE_ERROR;
		}

		return "";
	}
    

    /**
     * 判断视频
     * @param fileSize
     * @param fileContentType
     * @param fileHeader
     * @return
     */
    public static String checkVedio(long fileSize, String fileContentType, String fileHeader){
    	//判断文件类型
    	if(!FileTypeContants.FILE_TYPE_VEDIO.contains(fileContentType)){
			return FileTypeContants.FILE_TYPE_ERROR;
		}

    	//判断文件头
    	if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP4)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

    	//判断文件大小
    	if(fileSize > FileTypeContants.FILE_MAXSIZE_VEDIO){
    		return FileTypeContants.FILE_SIZE_ERROR;
		}
    	
    	return "";
    }

	public static String checkVedio(long fileSize, String fileHeader){

		//判断文件头
		if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP4)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

		//判断文件大小
		if(fileSize > FileTypeContants.FILE_MAXSIZE_VEDIO){
			return FileTypeContants.FILE_SIZE_ERROR;
		}

		return "";
	}

    

    /**
     * 判断PDF
     * @param fileSize
     * @param fileContentType
     * @param fileHeader
     * @return
     */
    public static String checkPDF(long fileSize,String fileContentType, String fileHeader){
    	//判断文件类型
    	if(!FileTypeContants.FILE_TYPE_PDF.contains(fileContentType)){
			return FileTypeContants.FILE_TYPE_ERROR;
		}
    	
    	//判断文件头
    	if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PDF)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

    	//判断文件大小
    	if(fileSize > FileTypeContants.FILE_MAXSIZE_PDF){
    		return FileTypeContants.FILE_SIZE_ERROR;
		}
    	
    	return "";
    }

	public static String checkPDF(long fileSize, String fileHeader){

		//判断文件头
		if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PDF)){
			return FileTypeContants.FILE_HEADER_ERROR;
		}

		//判断文件大小
		if(fileSize > FileTypeContants.FILE_MAXSIZE_PDF){
			return FileTypeContants.FILE_SIZE_ERROR;
		}

		return "";
	}
    
    /**
     * 根据文件路径获取文件头信息
     *
     * @param filePath
     *            文件路径
     * @return 文件头信息
     */
    public static String getFileHeader(MultipartFile file) {
        InputStream is = null;
        String value = "";
        try {
            is = file.getInputStream();
            byte[] b = new byte[16];
            is.read(b, 0, b.length);
            value = bytesToHexString(b);
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                	e.printStackTrace();
                }
            }
        }
        return value;
    }

	public static String getNormalFileHeader(File file) {
		InputStream is = null;
		String value = "";
		try {
			is = new FileInputStream(file);
			byte[] b = new byte[16];
			is.read(b, 0, b.length);
			value = bytesToHexString(b);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return value;
	}

    /**
     * 将要读取文件头信息的文件的byte数组转换成string类型表示
     *
     * @param src
     *            要读取文件头信息的文件的byte数组
     * @return 文件头十六进制信息
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder builder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        String hv;
        for (int i = 0; i < src.length; i++) {
            // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
            hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }
        System.out.println("HexString: " + builder.toString());
        return builder.toString();
    }
}

说明:
1.checkSafeMultipartFile方法,用来判断入参是MultipartFile的文件是否符合要求

2.checkSafeMultipartFiles方法,用来判断入参是MultipartFile数组的文件是否符合要求

3.调用这些工具方法后,会检查文件后缀是否符合要求、文件大小是否符合要求、文件流中的格式是否与文件名中的格式对应等,如果不符合要求,就返回错误信息;如果符合要求,就返回空字符串

4.可以看常量类,目前只允许word/excel/ppt/pdf/mp3/mp4等文件上传,可以自己根据需要扩展

3.使用样例代码

    @PostMapping("/mycontroller/oneUpload")
    public JSONObject OneExport(@RequestParam("file") MultipartFile file,@RequestParam String type) throws Exception {

        JSONObject backJson = new JSONObject();

        //文件必须是 word/excel/ppt/pdf/mp3/mp4
        String checkFile = FileTypeUtil.checkSafeMultipartFile(file, 
        		FileTypeContants.FILE_OFFICE, FileTypeContants.FILE_IMG, FileTypeContants.FILE_PDF,
        		FileTypeContants.FILE_AUDIO, FileTypeContants.FILE_VEDIO);
        
        if(!"".equals(checkFile)) {
            backJson.put("code", "-1");
            backJson.put("msg", "上传文件格式异常,请重新确认:" + checkFile);
            throw new Exception("上传文件格式异常,请重新确认:" + checkFile);
        }
        
        ......
        
        return backJson;
        
    }

说明:
假设上传文件页面,前端传来了要上传的文件MultipartFile file,先使用工具方法校验,如果目标文件不符合要求,那就报错,不允许上传;如果符合要求,那么才能执行后续的上传操作。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐梦想永不停

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值