java任意文件漏洞修復,使用文件魔數解決
背景: 客戶進行滲透測試,驗證上傳文件的程序沒有對上傳文件作任何過濾,導致可以上傳任意文件到服務器,甚至是病毒文件和Webshell木馬文件。
解決辦法:對于上傳的附件,驗證程序要做嚴格驗證,使用服務器端校驗,而不能僅用前端驗證。
代碼實例
// 允許上傳文件后綴
private static final String[] ALLOWED_FILE_EXTENSIONS = {"jpg", "jpeg", "png", "gif", "doc", "docx", "xls", "xlsx", "pdf", "ppt", "pptx"};
// 允許上傳文件頭魔數十六進制字符串
private static final List<String> ALLOWED_MAGIC_NUMBERS = Arrays.asList("FFD8FF", "89504E47", "47494638", "25504446", "D0CF11E0", "504B0304"
); // JPEG (jpg),PNG (png),GIF (gif),pdf,(doc、xls、ppt),(xls、pptx)// 允許上傳文件的MIME類型
private static final Set<String> ALLOWED_MIME_TYPES = new HashSet<>();
static {ALLOWED_MIME_TYPES.add("image/jpeg"); // jpg, jpeg ALLOWED_MIME_TYPES.add("image/png"); // png ALLOWED_MIME_TYPES.add("image/gif"); // gif ALLOWED_MIME_TYPES.add("application/msword"); // doc ALLOWED_MIME_TYPES.add("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); // docx ALLOWED_MIME_TYPES.add("application/vnd.ms-excel"); // xls ALLOWED_MIME_TYPES.add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // xlsx ALLOWED_MIME_TYPES.add("application/pdf"); // pdf ALLOWED_MIME_TYPES.add("application/vnd.ms-powerpoint"); // pptALLOWED_MIME_TYPES.add("application/vnd.openxmlformats-officedocument.presentationml.presentation"); // pptx
} @SuppressWarnings("unchecked")
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {request.setCharacterEncoding("UTF-8");String fileType="";// 1.創建文件上傳工廠類DiskFileItemFactory fac = new DiskFileItemFactory();// 2.創建文件上傳核心類對象ServletFileUpload upload = new ServletFileUpload(fac);// 【一、設置單個文件最大1024M】upload.setFileSizeMax(1024 * 1024 * 1024);// 1024MM// 【二、設置總文件大小:2048M】upload.setSizeMax(2048 * 1024 * 1024); // 2048MList<String> StringArr=new ArrayList<String>();// 判斷,當前表單是否為文件上傳表單if (upload.isMultipartContent(request)) {try {// 3.把請求數據轉換為FileItem對象的集合List<FileItem> list = upload.parseRequest(request);Calendar calendar = Calendar.getInstance();int year = calendar.get(Calendar.YEAR);// 遍歷,得到每一個上傳項for (FileItem item : list) {// 判斷:是普通表單項,還是文件上傳表單項if (item.isFormField()) {// 普通表單xString fieldName = item.getFieldName();// 獲取元素名稱String value = item.getString("UTF-8"); // 獲取元素值fileType=value;System.out.println(fieldName + " : " + value);} else {// 文件上傳表單//文件保存目錄路徑String savePath = getServletContext().getRealPath("/") + "uploadFiles/wjgl/"+fileType+"/"+year+"/";File uploadFile = new File(savePath);if (!uploadFile.exists()) {uploadFile.mkdirs();}String oldname = item.getName(); // 上傳的文件名稱String fileExtension = item.getName().substring(item.getName().lastIndexOf(".") + 1);//獲取到文件后綴if (!Arrays.asList(ALLOWED_FILE_EXTENSIONS).contains(fileExtension)) {//驗證文件后綴response.setStatus(500);throw new FileUploadException("if1無效的文件擴展名: " + fileExtension);}String contentType = item.getContentType();if(!ALLOWED_MIME_TYPES.contains(contentType)){response.setStatus(500);throw new FileUploadException("if2無效的文件擴展名: " + fileExtension);}if (!isFileValid(item)) {// 文件有效,進行處理response.setStatus(500);throw new FileUploadException("被改了后綴,判斷文件內容魔術無效的文件擴展名: " + fileExtension);}//時間戳String time=String.valueOf(new Date().getTime());String name = time+oldname;// 【三、上傳到指定目錄:獲取上傳目錄路徑】String realPath = "uploadFiles/wjgl/"+fileType+"/"+year+"/";// 創建文件對象File file = new File(savePath, name);item.write(file);item.delete();response.setContentType("application/json");response.setCharacterEncoding("UTF-8");JSONObject obj = new JSONObject();obj.put("fileName", oldname);obj.put("filePath", realPath + name);StringArr.add(obj.toString());}}response.getWriter().println(StringArr);} catch (Exception e) {e.printStackTrace();}} else {System.out.println("不處理!");}}//校驗是否為jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf,pptx格式
public static boolean isFileValid(FileItem fileItem) throws IOException {String fileName = fileItem.getName();try (InputStream inputStream = fileItem.getInputStream()) {return isValidFileMagicNumber(inputStream);}
}
//驗證文件魔數
public static boolean isValidFileMagicNumber(InputStream inputStream) throws IOException {boolean bl=false;byte[] buffer = new byte[8];inputStream.read(buffer, 0, 8);String hexMagicNumber = bytesToHex(buffer);for(int i = 0; i<ALLOWED_MAGIC_NUMBERS.size(); i++){String ms=ALLOWED_MAGIC_NUMBERS.get(i);if(hexMagicNumber.toUpperCase().startsWith(ms)){bl=true;break;}}return bl;
}
//字節轉換16進制
private static String bytesToHex(byte[] bytes) {StringBuilder hexString = new StringBuilder();for (byte b : bytes) {hexString.append(String.format("%02X", b));}return hexString.toString();
}