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

以你之姓@ 2024-02-18 11:05 109阅读 0赞

一、前言

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

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

再此总结下工具类方法。

二、代码

1.常量类

  1. import java.util.Arrays;
  2. import java.util.List;
  3. public class FileTypeContants {
  4. public static final String FILE_OFFICE = "office";//Word、Excel、PPT
  5. public static final String FILE_IMG = "img";//图片
  6. public static final String FILE_AUDIO = "audio";//音频
  7. public static final String FILE_VEDIO = "vedio";//视频
  8. public static final String FILE_PDF = "pdf";//pdf
  9. public static final String FILE_SUFFIX_ERROR = "文件后缀不正确或文件不存在";
  10. public static final String FILE_TYPE_ERROR = "文件类型不正确";
  11. public static final String FILE_HEADER_ERROR = "文件头不正确";
  12. public static final String FILE_SIZE_ERROR = "文件过大";
  13. //Word、Excel、PPT 文件后缀
  14. public static final List<String> FILE_SUFFIX_OFFICE = Arrays.asList("doc","docx", "xls","xlsx","ppt","pptx");
  15. //图片 文件后缀
  16. public static final List<String> FILE_SUFFIX_IMG = Arrays.asList("png","jpeg","jpg");
  17. //音频 文件后缀(目前视频只支持MP3)
  18. public static final List<String> FILE_SUFFIX_AUDIO = Arrays.asList("mp3");
  19. //视频 文件后缀(目前视频只支持MP4)
  20. public static final List<String> FILE_SUFFIX_VEDIO = Arrays.asList("mp4");
  21. //PDF 文件后缀
  22. public static final List<String> FILE_SUFFIX_PDF = Arrays.asList("pdf");
  23. //Word、Excel、PPT 文件类型
  24. public static final List<String> FILE_TYPE_OFFICE = Arrays.asList(
  25. "application/msword",//doc
  26. "application/vnd.openxmlformats-officedocument.wordprocessingml.document",//docx
  27. "application/vnd.ms-excel",//xls
  28. "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",//xlsx
  29. "application/vnd.ms-powerpoint",//ppt
  30. "application/vnd.openxmlformats-officedocument.presentationml.presentation"//pptx
  31. );
  32. //图片 文件类型
  33. public static final List<String> FILE_TYPE_IMG = Arrays.asList("image/png","image/jpeg");//jpeg jpg 是一样的
  34. //音频 文件类型
  35. public static final List<String> FILE_TYPE_AUDIO = Arrays.asList("audio/mpeg");
  36. //视频 文件类型
  37. public static final List<String> FILE_TYPE_VEDIO = Arrays.asList("video/mp4");
  38. //pdf 文件类型
  39. public static final List<String> FILE_TYPE_PDF = Arrays.asList("application/pdf");
  40. //不同类型文件的文件头判断长度不一样
  41. //Word、Excel、PPT 文件头
  42. public static final String FILE_HEADER_OFFICE = "D0CF11E0";//doc、xls、ppt
  43. public static final String FILE_HEADER_OFFICEX = "504B0304";//docx、xlsx、pptx
  44. //图片 文件头
  45. public static final String FILE_HEADER_PNG = "89504E47";
  46. public static final String FILE_HEADER_JPG = "FFD8FF";//jpeg jpg 是一样的
  47. //音频 文件头
  48. public static final String FILE_HEADER_MP3 = "494433";
  49. /**
  50. * MP4 文件头有很多种,先只取 000000
  51. * 00 00 00 14 66 74 79 70 69 73 6f 6d
  52. 00 00 00 18 66 74 79 70
  53. 00 00 00 1c 66 74 79 70
  54. 00 00 00 20 66 74 79 70 4d 34 41
  55. */
  56. public static final String FILE_HEADER_MP4 = "000000";
  57. //pdf 文件头
  58. public static final String FILE_HEADER_PDF = "25504446";
  59. //Word、Excel、PPT 文件大小
  60. public static final long FILE_MAXSIZE_OFFICE = 30 * 1024 ; // Word/Excel/PPT 都限制30M
  61. //图片 大小
  62. public static final long FILE_MAXSIZE_IMG = 10 * 1024;//图片限制10M
  63. //音频 文件大小
  64. public static final long FILE_MAXSIZE_AUDIO = 30 * 1024;//MP3限制30M
  65. //视频 文件大小
  66. public static final long FILE_MAXSIZE_VEDIO = 2 * 1024 * 1024;//MP4限制2G
  67. //pdf 文件大小
  68. public static final long FILE_MAXSIZE_PDF = 20 * 1024;//PDF限制10M
  69. //其他备用
  70. public static final String FILE_TYPE_JSON = "application/json";
  71. public static final String FILE_TYPE_XML = "application/xml";
  72. public static final String FILE_TYPE_SVG = "image/svg";
  73. }

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

2.工具类

  1. import org.springframework.stereotype.Component;
  2. import org.springframework.web.multipart.MultipartFile;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. @Component
  8. public class FileTypeUtil {
  9. /**
  10. * 判断文件是否符合要求
  11. * 文件后缀、文件类型、文件头、文件大小
  12. * @param file
  13. * @param fileType
  14. * @return
  15. */
  16. public static String checkSafeMultipartFile(MultipartFile file, String...fileTypes){
  17. if(file != null && fileTypes != null && fileTypes.length > 0){
  18. //文件后缀
  19. String fileName = file.getOriginalFilename();
  20. Integer index = fileName.lastIndexOf(".");
  21. String suffix = fileName.substring(index+1).toLowerCase();
  22. //文件大小
  23. long fileSize = file.getSize()/1024;
  24. //文件类型
  25. String fileContentType = file.getContentType();
  26. //文件头
  27. String fileHeader = FileTypeUtil.getFileHeader(file);
  28. for(String fileType : fileTypes){
  29. if(FileTypeContants.FILE_OFFICE.equals(fileType) && FileTypeContants.FILE_SUFFIX_OFFICE.contains(suffix)){
  30. //Word、Excel、PPT
  31. return FileTypeUtil.checkOffice(fileSize,fileContentType,fileHeader);
  32. }else if(FileTypeContants.FILE_IMG.equals(fileType) && FileTypeContants.FILE_SUFFIX_IMG.contains(suffix)){
  33. //图片
  34. return FileTypeUtil.checkImg(fileSize,fileContentType,fileHeader);
  35. }else if(FileTypeContants.FILE_AUDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_AUDIO.contains(suffix)){
  36. //音频
  37. return FileTypeUtil.checkAudio(fileSize,fileContentType,fileHeader);
  38. }else if(FileTypeContants.FILE_VEDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_VEDIO.contains(suffix)){
  39. //视频
  40. return FileTypeUtil.checkVedio(fileSize,fileContentType,fileHeader);
  41. }else if(FileTypeContants.FILE_PDF.equals(fileType) && FileTypeContants.FILE_SUFFIX_PDF.contains(suffix)){
  42. //PDF
  43. return FileTypeUtil.checkPDF(fileSize,fileContentType,fileHeader);
  44. }
  45. }
  46. }
  47. return FileTypeContants.FILE_SUFFIX_ERROR;
  48. }
  49. public static String checkSafeNormalFile(File file, String...fileTypes){
  50. if(file != null && fileTypes != null && file.exists() && fileTypes.length > 0){
  51. //文件后缀
  52. String fileName = file.getName();
  53. Integer index = fileName.lastIndexOf(".");
  54. String suffix = fileName.substring(index+1).toLowerCase();
  55. //文件大小
  56. long fileSize = file.length()/1024;
  57. //文件类型,普通文件没有这个
  58. //String fileContentType = file.getContentType();
  59. //文件头
  60. String fileHeader = FileTypeUtil.getNormalFileHeader(file);
  61. for(String fileType : fileTypes){
  62. if(FileTypeContants.FILE_OFFICE.equals(fileType) && FileTypeContants.FILE_SUFFIX_OFFICE.contains(suffix)){
  63. //Word、Excel、PPT
  64. return FileTypeUtil.checkOffice(fileSize, fileHeader);
  65. }else if(FileTypeContants.FILE_IMG.equals(fileType) && FileTypeContants.FILE_SUFFIX_IMG.contains(suffix)){
  66. //图片
  67. return FileTypeUtil.checkImg(fileSize, fileHeader);
  68. }else if(FileTypeContants.FILE_AUDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_AUDIO.contains(suffix)){
  69. //音频
  70. return FileTypeUtil.checkAudio(fileSize, fileHeader);
  71. }else if(FileTypeContants.FILE_VEDIO.equals(fileType) && FileTypeContants.FILE_SUFFIX_VEDIO.contains(suffix)){
  72. //视频
  73. return FileTypeUtil.checkVedio(fileSize, fileHeader);
  74. }else if(FileTypeContants.FILE_PDF.equals(fileType) && FileTypeContants.FILE_SUFFIX_PDF.contains(suffix)){
  75. //PDF
  76. return FileTypeUtil.checkPDF(fileSize, fileHeader);
  77. }
  78. }
  79. }
  80. return FileTypeContants.FILE_SUFFIX_ERROR;
  81. }
  82. /**
  83. * 判断文件是否符合要求,多个文件
  84. * 文件后缀、文件类型、文件头、文件大小
  85. * @param files
  86. * @param fileTypes
  87. * @return
  88. */
  89. public static String checkSafeMultipartFiles(MultipartFile[] files, String...fileTypes){
  90. String checkMessage = "";
  91. for(MultipartFile file : files){
  92. checkMessage = FileTypeUtil.checkSafeMultipartFile(file, fileTypes);
  93. if(!"".equals(checkMessage)){
  94. return checkMessage;
  95. }
  96. }
  97. return checkMessage;
  98. }
  99. /**
  100. * 判断 Word、Excel、PPT
  101. * @param fileSize
  102. * @param fileContentType
  103. * @param fileHeader
  104. * @return
  105. */
  106. public static String checkOffice(long fileSize,String fileContentType ,String fileHeader){
  107. //判断文件类型
  108. if(!FileTypeContants.FILE_TYPE_OFFICE.contains(fileContentType)){
  109. return FileTypeContants.FILE_TYPE_ERROR;
  110. }
  111. //判断文件头
  112. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICE)
  113. && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICEX)){
  114. return FileTypeContants.FILE_HEADER_ERROR;
  115. }
  116. //判断文件大小
  117. if(fileSize > FileTypeContants.FILE_MAXSIZE_OFFICE){
  118. return FileTypeContants.FILE_SIZE_ERROR;
  119. }
  120. return "";
  121. }
  122. public static String checkOffice(long fileSize, String fileHeader){
  123. //判断文件头
  124. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICE)
  125. && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_OFFICEX)){
  126. return FileTypeContants.FILE_HEADER_ERROR;
  127. }
  128. //判断文件大小
  129. if(fileSize > FileTypeContants.FILE_MAXSIZE_OFFICE){
  130. return FileTypeContants.FILE_SIZE_ERROR;
  131. }
  132. return "";
  133. }
  134. /**
  135. * 判断图片
  136. * @param fileSize
  137. * @param fileContentType
  138. * @param fileHeader
  139. * @return
  140. */
  141. public static String checkImg(long fileSize, String fileContentType, String fileHeader){
  142. //判断文件类型
  143. if(!FileTypeContants.FILE_TYPE_IMG.contains(fileContentType)){
  144. return FileTypeContants.FILE_TYPE_ERROR;
  145. }
  146. //判断文件头
  147. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PNG)
  148. && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_JPG)){
  149. return FileTypeContants.FILE_HEADER_ERROR;
  150. }
  151. //判断文件大小
  152. if(fileSize > FileTypeContants.FILE_MAXSIZE_IMG){
  153. return FileTypeContants.FILE_SIZE_ERROR;
  154. }
  155. return "";
  156. }
  157. public static String checkImg(long fileSize, String fileHeader){
  158. //判断文件头
  159. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PNG)
  160. && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_JPG)){
  161. return FileTypeContants.FILE_HEADER_ERROR;
  162. }
  163. //判断文件大小
  164. if(fileSize > FileTypeContants.FILE_MAXSIZE_IMG){
  165. return FileTypeContants.FILE_SIZE_ERROR;
  166. }
  167. return "";
  168. }
  169. /**
  170. * 判断音频
  171. * @param fileSize
  172. * @param fileContentType
  173. * @param fileHeader
  174. * @return
  175. */
  176. public static String checkAudio(long fileSize, String fileContentType, String fileHeader){
  177. //判断文件类型
  178. if(!FileTypeContants.FILE_TYPE_AUDIO.contains(fileContentType)){
  179. return FileTypeContants.FILE_TYPE_ERROR;
  180. }
  181. //判断文件头
  182. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP3)){
  183. return FileTypeContants.FILE_HEADER_ERROR;
  184. }
  185. //判断文件大小
  186. if(fileSize > FileTypeContants.FILE_MAXSIZE_AUDIO){
  187. return FileTypeContants.FILE_SIZE_ERROR;
  188. }
  189. return "";
  190. }
  191. public static String checkAudio(long fileSize, String fileHeader){
  192. //判断文件头
  193. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP3)){
  194. return FileTypeContants.FILE_HEADER_ERROR;
  195. }
  196. //判断文件大小
  197. if(fileSize > FileTypeContants.FILE_MAXSIZE_AUDIO){
  198. return FileTypeContants.FILE_SIZE_ERROR;
  199. }
  200. return "";
  201. }
  202. /**
  203. * 判断视频
  204. * @param fileSize
  205. * @param fileContentType
  206. * @param fileHeader
  207. * @return
  208. */
  209. public static String checkVedio(long fileSize, String fileContentType, String fileHeader){
  210. //判断文件类型
  211. if(!FileTypeContants.FILE_TYPE_VEDIO.contains(fileContentType)){
  212. return FileTypeContants.FILE_TYPE_ERROR;
  213. }
  214. //判断文件头
  215. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP4)){
  216. return FileTypeContants.FILE_HEADER_ERROR;
  217. }
  218. //判断文件大小
  219. if(fileSize > FileTypeContants.FILE_MAXSIZE_VEDIO){
  220. return FileTypeContants.FILE_SIZE_ERROR;
  221. }
  222. return "";
  223. }
  224. public static String checkVedio(long fileSize, String fileHeader){
  225. //判断文件头
  226. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_MP4)){
  227. return FileTypeContants.FILE_HEADER_ERROR;
  228. }
  229. //判断文件大小
  230. if(fileSize > FileTypeContants.FILE_MAXSIZE_VEDIO){
  231. return FileTypeContants.FILE_SIZE_ERROR;
  232. }
  233. return "";
  234. }
  235. /**
  236. * 判断PDF
  237. * @param fileSize
  238. * @param fileContentType
  239. * @param fileHeader
  240. * @return
  241. */
  242. public static String checkPDF(long fileSize,String fileContentType, String fileHeader){
  243. //判断文件类型
  244. if(!FileTypeContants.FILE_TYPE_PDF.contains(fileContentType)){
  245. return FileTypeContants.FILE_TYPE_ERROR;
  246. }
  247. //判断文件头
  248. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PDF)){
  249. return FileTypeContants.FILE_HEADER_ERROR;
  250. }
  251. //判断文件大小
  252. if(fileSize > FileTypeContants.FILE_MAXSIZE_PDF){
  253. return FileTypeContants.FILE_SIZE_ERROR;
  254. }
  255. return "";
  256. }
  257. public static String checkPDF(long fileSize, String fileHeader){
  258. //判断文件头
  259. if(fileHeader !=null && !fileHeader.startsWith(FileTypeContants.FILE_HEADER_PDF)){
  260. return FileTypeContants.FILE_HEADER_ERROR;
  261. }
  262. //判断文件大小
  263. if(fileSize > FileTypeContants.FILE_MAXSIZE_PDF){
  264. return FileTypeContants.FILE_SIZE_ERROR;
  265. }
  266. return "";
  267. }
  268. /**
  269. * 根据文件路径获取文件头信息
  270. *
  271. * @param filePath
  272. * 文件路径
  273. * @return 文件头信息
  274. */
  275. public static String getFileHeader(MultipartFile file) {
  276. InputStream is = null;
  277. String value = "";
  278. try {
  279. is = file.getInputStream();
  280. byte[] b = new byte[16];
  281. is.read(b, 0, b.length);
  282. value = bytesToHexString(b);
  283. } catch (Exception e) {
  284. e.printStackTrace();
  285. } finally {
  286. if (null != is) {
  287. try {
  288. is.close();
  289. } catch (IOException e) {
  290. e.printStackTrace();
  291. }
  292. }
  293. }
  294. return value;
  295. }
  296. public static String getNormalFileHeader(File file) {
  297. InputStream is = null;
  298. String value = "";
  299. try {
  300. is = new FileInputStream(file);
  301. byte[] b = new byte[16];
  302. is.read(b, 0, b.length);
  303. value = bytesToHexString(b);
  304. } catch (Exception e) {
  305. e.printStackTrace();
  306. } finally {
  307. if (null != is) {
  308. try {
  309. is.close();
  310. } catch (IOException e) {
  311. e.printStackTrace();
  312. }
  313. }
  314. }
  315. return value;
  316. }
  317. /**
  318. * 将要读取文件头信息的文件的byte数组转换成string类型表示
  319. *
  320. * @param src
  321. * 要读取文件头信息的文件的byte数组
  322. * @return 文件头十六进制信息
  323. */
  324. private static String bytesToHexString(byte[] src) {
  325. StringBuilder builder = new StringBuilder();
  326. if (src == null || src.length <= 0) {
  327. return null;
  328. }
  329. String hv;
  330. for (int i = 0; i < src.length; i++) {
  331. // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
  332. hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
  333. if (hv.length() < 2) {
  334. builder.append(0);
  335. }
  336. builder.append(hv);
  337. }
  338. System.out.println("HexString: " + builder.toString());
  339. return builder.toString();
  340. }
  341. }

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

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

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

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

3.使用样例代码

  1. @PostMapping("/mycontroller/oneUpload")
  2. public JSONObject OneExport(@RequestParam("file") MultipartFile file,@RequestParam String type) throws Exception {
  3. JSONObject backJson = new JSONObject();
  4. //文件必须是 word/excel/ppt/pdf/mp3/mp4
  5. String checkFile = FileTypeUtil.checkSafeMultipartFile(file,
  6. FileTypeContants.FILE_OFFICE, FileTypeContants.FILE_IMG, FileTypeContants.FILE_PDF,
  7. FileTypeContants.FILE_AUDIO, FileTypeContants.FILE_VEDIO);
  8. if(!"".equals(checkFile)) {
  9. backJson.put("code", "-1");
  10. backJson.put("msg", "上传文件格式异常,请重新确认:" + checkFile);
  11. throw new Exception("上传文件格式异常,请重新确认:" + checkFile);
  12. }
  13. ......
  14. return backJson;
  15. }

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

发表评论

表情:
评论列表 (有 0 条评论,109人围观)

还没有评论,来说两句吧...

相关阅读