任意文件上传(高危)

# 漏洞等级 高危 # 漏洞描述 现在大多数网站都有文件上传功能,用于上传文档、图片等操作。但是未对上传的文件进行严格的验证和过滤,就有可能导致攻击者上传恶意脚本文件,获取信息、控制网站,甚至控制服务器 # 漏洞危害 上传恶意脚本,获取信息、控制网站,甚至控制服务器 # 修复建议 (注意全局修复) 1、在服务器上使用白名单对上传的文件进行严格的验证,只允许上传特定的文件类型。验证包括文件扩展名校验、文件类型(Content-Type)校验、MIME类型校验等等。 2、上传文件的存储目录禁用执行权限 # 修复参考代码 ```java import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSON; import com.uwit.bsdigistersystem.modules.recordsconfig.constant.DictUtils; import com.uwit.bsdigistersystem.modules.recordsconfig.entity.Dictdata; import com.uwit.common.utils.ResultUtil; import com.uwit.common.utils.file.Extension; import lombok.extern.slf4j.Slf4j; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.io.IOUtils; import org.springframework.http.MediaType; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; import java.util.stream.Collectors; /** * @Description 文件上传拦截器 * @Author long * @Version V1.0.0 * @Since 1.0 * @Date 2022/5/27 */ @Slf4j public class FileHeaderCheckInterceptorImpl implements HandlerInterceptor { /** * 目标方法执行以前 * * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); // 判断是否为文件上传请求 if (commonsMultipartResolver.isMultipart(request)) { MultipartResolver resovler = new StandardServletMultipartResolver(); MultipartHttpServletRequest multipartRequest = resovler.resolveMultipart(request); Map<String, MultipartFile> files = multipartRequest.getFileMap(); for (String formKey : files.keySet()) { MultipartFile multipartFile = multipartRequest.getFile(formKey); String fileExt = Extension.getFileExtensionByMultipartFile(multipartFile); // 后缀判断 boolean fixPass = this.checkFileSuffix(multipartFile); if (!fixPass) { this.responseError(response); log.error(fileExt + "此文件后缀系统不允许上传"); return false; } // 文件头判断 if (!this.checkFileHeader(multipartFile)) { this.responseError(response); String fileHead = getFileHeader(multipartFile); log.error(fileHead + "此文件头不在系统允许列表内"); return false; } } } return true; } /** * 获取前8位文件头 * @param multipartFile * @return 文件头 */ public static String getFileHeader(MultipartFile multipartFile) { byte[] file = new byte[0]; try { file = multipartFile.getBytes(); } catch (IOException e) { e.printStackTrace(); } int HEADER_LENGTH = 4; if (file.length > HEADER_LENGTH) { //转成16进制 StringBuilder sb = new StringBuilder(); for (int i = 0; i < HEADER_LENGTH; i++) { int v = file[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { sb.append(0); } sb.append(hv); } return sb.toString(); } return ""; } /** * 检测文件头 * * @param multipartFile 上传的文件对象 * @return true: 符合,false: 不符合 */ public Boolean checkFileHeader(MultipartFile multipartFile) { // 验证文件头 String fileHead = getFileHeader(multipartFile); fileHead = fileHead.toLowerCase(); Set<String> systemAllowFileHeardSet = getSystemAllowFileHeardSet(); return systemAllowFileHeardSet.contains(fileHead); } /** * 获取系统允许上传的问题文件类型后缀列表 * @return 系统允许上传的文件后缀列表 */ public Set<String> getSystemAllowFileTypeExtSet(){ List<Dictdata> sysFormatExtList = DictUtils.getDictCache("documentFormat"); if(CollectionUtil.isEmpty(sysFormatExtList)){ sysFormatExtList = new ArrayList<>(); } return sysFormatExtList.stream() .map(Dictdata::getText) .map(String::toLowerCase) .collect(Collectors.toSet()); } /** * 获取系统允许上传的问题文文件头列表 * @return 系统允许上传的文件头列表 */ public Set<String> getSystemAllowFileHeardSet(){ List<Dictdata> sysFormatExtList = DictUtils.getDictCache("documentFormat"); if(CollectionUtil.isEmpty(sysFormatExtList)){ sysFormatExtList = new ArrayList<>(); } return sysFormatExtList.stream() .map(Dictdata::getValue) .map(String::toLowerCase) .collect(Collectors.toSet()); } /** * 文件后缀判断 * * @param multipartFile 文件对象 * @return true :符合,false:不符合 */ public Boolean checkFileSuffix(MultipartFile multipartFile) { assert multipartFile != null; String fileExt = Extension.getFileExtensionByMultipartFile(multipartFile); fileExt = fileExt.toLowerCase(); Set<String> systemExtSet = getSystemAllowFileTypeExtSet(); return systemExtSet.contains(fileExt); } public void responseError(HttpServletResponse response) { log.error("----------非系统可信文档类型,不支持采集上传!"); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=utf-8"); response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Cache-Control", "no-cache"); try { response.getWriter().println(JSON.toJSONString(ResultUtil.error("500", "非系统可信文档类型,不支持采集上传!"))); response.getWriter().flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 目标方法执行以后 * * @param request 请求 * @param response 响应 * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 渲染以后 * * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } ```