對外接口支持文件上傳功能時,為避免有人惡意上傳有毒或者篡改程序的腳本,需要對上傳的文件添加安全性校驗。
1.文件后綴校驗
文件首先校驗直觀文件上傳格式,校驗文件后綴是否符合業(yè)務(wù)要求。以MultipartFile類為例
String fileName = file.getOriginalFilename();
if (Strings.isEmpty(fileName)) {
throw new RuntimeException("文件名未找到");
}
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
if (!Objects.equal(suffix, "xls") && !Objects.equal(suffix, "xlsx")) {
throw new RuntimeException("文件類型不正確,需要為xls或者xlsx");
}
2.校驗文件頭
由于文件后綴可能涉及到篡改的情況出現(xiàn),因此需要校驗文件的魔數(shù),也就是文件頭。無論這個文件是否修改文件后綴,這個文件的文件頭是不會改變的。下面是常用的文件頭格式:
1.使用枚舉類去校驗
JPEG (jpg),文件頭:FFD8FFE1
PNG (png),文件頭:89504E47
GIF (gif),文件頭:47494638
TIFF (tif),文件頭:49492A00
Windows Bitmap (bmp),文件頭:424D
CAD (dwg),文件頭:41433130
Adobe Photoshop (psd),文件頭:38425053
Rich Text Format (rtf),文件頭:7B5C727466
XML (xml),文件頭:3C3F786D6C
HTML (html),文件頭:68746D6C3E
Email [thorough only] (eml),文件頭:44656C69766572792D646174653A
Outlook Express (dbx),文件頭:CFAD12FEC5FD746F
Outlook (pst),文件頭:2142444E
MS Word/Excel (xls.or.doc),文件頭:D0CF11E0
MS Access (mdb),文件頭:5374616E64617264204A
WordPerfect (wpd),文件頭:FF575043
Postscript (eps.or.ps),文件頭:252150532D41646F6265
Adobe Acrobat (pdf),文件頭:255044462D312E
Quicken (qdf),文件頭:AC9EBD8F
Windows Password (pwl),文件頭:E3828596
ZIP Archive (zip),文件頭:504B0304
RAR Archive (rar),文件頭:52617221
Wave (wav),文件頭:57415645
AVI (avi),文件頭:41564920
Real Audio (ram),文件頭:2E7261FD
Real Media (rm),文件頭:2E524D46
MPEG (mpg),文件頭:000001BA
MPEG (mpg),文件頭:000001B3
Quicktime (mov),文件頭:6D6F6F76
Windows Media (asf),文件頭:3026B2758E66CF11
MIDI (mid),文件頭:4D546864
使用上面的文件頭去校驗的代碼示例(這段代碼參考:Java 實戰(zhàn)系列·Magic 魔數(shù)獲取文件類型):
1.魔數(shù)枚舉類
public enum FileType {
/**
* JPEG
*/
JPEG("JPEG", "FFD8FF"),
/**
* PNG
*/
PNG("PNG", "89504E47"),
/**
* GIF
*/
GIF("GIF", "47494638"),
/**
* TIFF
*/
TIFF("TIFF", "49492A00"),
/**
* Windows bitmap
*/
BMP("BMP", "424D"),
/**
* CAD
*/
DWG("DWG", "41433130"),
/**
* Adobe photoshop
*/
PSD("PSD", "38425053"),
/**
* Rich Text Format
*/
RTF("RTF", "7B5C727466"),
/**
* XML
*/
XML("XML", "3C3F786D6C"),
/**
* HTML
*/
HTML("HTML", "68746D6C3E"),
/**
* Outlook Express
*/
DBX("DBX", "CFAD12FEC5FD746F "),
/**
* Outlook
*/
PST("PST", "2142444E"),
/**
* doc;xls;dot;ppt;xla;ppa;pps;pot;msi;sdw;db
*/
OLE2("OLE2", "0xD0CF11E0A1B11AE1"),
/**
* Microsoft Word/Excel
*/
XLS_DOC("XLS_DOC", "D0CF11E0"),
/**
* Microsoft Access
*/
MDB("MDB", "5374616E64617264204A"),
/**
* Word Perfect
*/
WPB("WPB", "FF575043"),
/**
* Postscript
*/
EPS_PS("EPS_PS", "252150532D41646F6265"),
/**
* Adobe Acrobat
*/
PDF("PDF", "255044462D312E"),
/**
* Windows Password
*/
PWL("PWL", "E3828596"),
/**
* ZIP Archive
*/
ZIP("ZIP", "504B0304"),
/**
* ARAR Archive
*/
RAR("RAR", "52617221"),
/**
* WAVE
*/
WAV("WAV", "57415645"),
/**
* AVI
*/
AVI("AVI", "41564920"),
/**
* Real Audio
*/
RAM("RAM", "2E7261FD"),
/**
* Real Media
*/
RM("RM", "2E524D46"),
/**
* Quicktime
*/
MOV("MOV", "6D6F6F76"),
/**
* Windows Media
*/
ASF("ASF", "3026B2758E66CF11"),
/**
* MIDI
*/
MID("MID", "4D546864");
private String key;
private String value;
FileType(String key, String value) {
this.key = key;
this.value = value;
}
public String getValue() {
return value;
}
public String getKey() {
return key;
}
}
2.獲取文件頭,并校驗是否為Excel
public class FileUtil {
/**
* 獲取文件投
*
* @param filePath 文件路徑
* @return 16 進(jìn)制的文件投信息
*
* @throws IOException
*/
private static String getFileHeader(String filePath) throws IOException {
byte[] b = new byte[28];
InputStream inputStream = new FileInputStream(filePath);
inputStream.read(b, 0, 28);
inputStream.close();
return bytes2hex(b);
}
/**
* 將字節(jié)數(shù)組轉(zhuǎn)換成16進(jìn)制字符串
*/
private static String bytes2hex(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (byte b : src) {
int v = b & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 校驗是否為excel
*
* @param filePath 文件路徑
* @return 文件類型
*
* @throws IOException
*/
public static boolean checkIsExcel(String filePath) throws IOException {
String fileHead = getFileHeader(filePath);
if (null == fileHead || fileHead.length() == 0) {
return false;
}
//校驗是否為xls或者xlsx文件
if (Objects.equal(fileHead, FileType.OLE2.getValue()) || Objects.equal(fileHead, FileType.XLS_DOC.getValue())) {
return true;
}
return false;
}
}
除了用上面的魔數(shù)頭去校驗,也可以用poi提供的枚舉類FileMagic工具類去校驗:
2.FileMagic校驗文件頭
FileMagic魔數(shù)值解釋:
OLE2(-2226271756974174256L), //xls
OOXML(new int[]{80, 75, 3, 4}), //xlsx, OOXML全稱是Office Open XML,OOXML是由微軟公司為Office 2007產(chǎn)品開發(fā)的技術(shù)規(guī)范,現(xiàn)已成為國際文檔格式標(biāo)準(zhǔn),兼容前國際標(biāo)準(zhǔn)ODF(Open Document Format)和中國文檔標(biāo)準(zhǔn)UOF(Unified Office document Format)。
XML(new int[]{60, 63, 120, 109, 108}), //xml
BIFF2(new int[]{9, 0, 4, 0, 0, 0, 63, 0}), //Excel 2 現(xiàn)在office已經(jīng)不支持
BIFF3(new int[]{9, 2, 6, 0, 0, 0, 63, 0}),//Excel 3現(xiàn)在office已經(jīng)不支持
BIFF4(new byte[][]{{9, 4, 6, 0, 0, 0, 63, 0}, {9, 4, 6, 0, 0, 0, 0, 1}}),//Excel 4 現(xiàn)在office已經(jīng)不支持
MSWRITE(new byte[][]{{49, -66, 0, 0}, {50, -66, 0, 0}}), //微軟原來的寫入流,這個不清楚是否還能使用。
RTF(new String[]{"{\\rtf"}), //rtf
PDF(new String[]{"%PDF"}), //pdf
HTML(new String[]{"<!DOCTYP", "<html", "\n\r<html", "\r\n<html", "\r<html", "\n<html", "<HTML", "\r\n<HTML", "\n\r<HTML", "\r<HTML", "\n<HTML"}), //HTML
WORD2(new int[]{219, 165, 45, 0}),//word
JPEG(new byte[][]{{-1, -40, -1, -37}, {-1, -40, -1, -32, 63, 63, 74, 70, 73, 70, 0, 1}, {-1, -40, -1, -18}, {-1, -40, -1, -31, 63, 63, 69, 120, 105, 102, 0, 0}}),//圖片驗證,jpeg格式
GIF(new String[]{"GIF87a", "GIF89a"}),//圖片驗證,gif格式
PNG(new int[]{137, 80, 78, 71, 13, 10, 26, 10}),//圖片驗證,png格式
TIFF(new String[]{"II*\u0000", "MM\u0000*"}),//圖片驗證,tiff格式
WMF(new int[]{215, 205, 198, 154}),//圖片驗證,wmf格式
EMF(new int[]{1, 0, 0, 0, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 32, 69, 77, 70}),//圖片驗證,emf格式
BMP(new int[]{66, 77}),//圖片驗證,nmp格式
UNKNOWN(new byte[][]{new byte[0]});//未知魔數(shù)
使用魔數(shù)校驗Excel文件代碼:
private static boolean checkIsExcel(InputStream inputStream) throws IOException {
//獲取文件流的文件頭
FileMagic fileMagic = FileMagic.valueOf(inputStream);
//判斷Excel文件頭是否符合xls或者xlsx
if (Objects.equal(fileMagic, FileMagic.OLE2) || Objects.equal(fileMagic, FileMagic.OOXML)) {
return true;
}
return false;
}
3.校驗文件大小
為了避免上傳過大文件,影響服務(wù)器性能以及帶寬。需要對文件大小進(jìn)行校驗,具體文件大小控制以業(yè)務(wù)為主。文章來源:http://www.zghlxwxcb.cn/news/detail-441763.html
4.示例
以校驗Excel文件為例:文章來源地址http://www.zghlxwxcb.cn/news/detail-441763.html
/**
* 校驗文件
*
* @param file 文件
* @param fileMaxSize 文件大小限制
*/
public static void checkExcel(MultipartFile file, Double fileMaxSize) {
// 文件類型判斷 - 校驗文件后綴
String fileName = file.getOriginalFilename();
if (Strings.isEmpty(fileName)) {
throw new RuntimeException("文件名未找到");
}
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
if (!Objects.equal(suffix, "xls") && !Objects.equal(suffix, "xlsx")) {
throw new RuntimeException("文件類型不正確,需要為xls或者xlsx");
}
// 文件類型判斷 - 校驗文件頭內(nèi)容
try (InputStream inputStream = file.getInputStream()) {
// 獲取到上傳文件的文件頭信息
boolean isExcel = checkIsExcel(inputStream);
if (!isExcel) {
throw new RuntimeException("文件類型不正確,原文件類型需要為xls");
}
} catch (IOException e) {
log.error("Get file input stream failed.", e);
throw new RuntimeException("文件上傳失敗");
}
// 文件大小校驗 - 單位:MB
long fileBytes = file.getSize();
double fileSize = (double) fileBytes / 1048576;
if (fileSize <= 0) {
throw new RuntimeException("文件內(nèi)容為空");
}
if (fileSize > fileMaxSize) {
throw new RuntimeException("文件上傳內(nèi)容大小超出限制");
}
}
/**
* 校驗文件頭
*
* @param inputStream
* @return
*
* @throws IOException
*/
private static boolean checkIsExcel(InputStream inputStream) throws IOException {
FileMagic fileMagic = FileMagic.valueOf(inputStream);
if (Objects.equal(fileMagic, FileMagic.OLE2) || Objects.equal(fileMagic, FileMagic.OOXML)) {
return true;
}
return false;
}
到了這里,關(guān)于10-java實現(xiàn)對上傳文件做安全性檢查的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!