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)){
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)){
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
,先使用工具方法校验,如果目标文件不符合要求,那就报错,不允许上传;如果符合要求,那么才能执行后续的上传操作。
还没有评论,来说两句吧...