文件上传的漏洞需要解决,需要使用过滤器来实现。在过滤器中对文件进行判断处理
遇到一个问题,我这里读取了一遍文件之后,request流被读完了,导致真正获取文件的时候,获取不到
使用包装类,重写
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
/*StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}*/
body = IOUtils.toByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
ServletInputStream servletInputStream = new ServletInputStream() {
public boolean isFinished() {
return false;
}
public boolean isReady() {
return false;
}
/*public void setReadListener(ReadListener readListener) {}*/
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
// public String getBody() {
// return this.body;
// }
public byte[] getBody() {
return this.body;
}
}
调用
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=utf-8");
//判断提交上来的数据是否是上传表单的数据
if (!ServletFileUpload.isMultipartContent(request)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
HttpServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if (requestWrapper != null) {
try {
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> fileList = upload.parseRequest(requestWrapper);
//上传文件对象集合
//TODO:这个用一个集合来接收,因为会对 FileItem的文件名进行校验,但是 上传文件时,FileItem会有一个无效的文件。所以这里先把有效的存储一遍。
List<FileItem> fileItemsList = new ArrayList<FileItem>();
//临时表单控件对象
FileItem fileItem = null;
Iterator<FileItem> it = fileList.iterator();
while (it.hasNext()) {
fileItem = (FileItem) it.next();
// 获取上传号码文件
if (!fileItem.isFormField() && fileItem.getName().length() > 0) {
//有效文件对象存放到集合
fileItemsList.add(fileItem);
}
}
//进行过滤
for (FileItem item : fileItemsList) {
//如果fileitem中封装的是上传文件
if (!item.isFormField()) {
UploadFileCheckUtil.fileCheck(item);
}
}
filterChain.doFilter(requestWrapper, servletResponse);
return;
}catch (Exception e) {
logger.error( e, "过滤文件异常");
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
文件上传校验工具 UploadFileCheckUtil
package com.montnets.emp.common.util;
import org.apache.commons.fileupload.FileItem;
import org.eclipse.jetty.util.ConcurrentHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 文件上传校验
*
* @author : raymond
* @version : V1.0
* @date : 2022-01-14 11:23
*/
public class UploadFileCheckUtil {
/**
* 文件类型白名单
*/
private final static Set<String> FILE_NAME_WHITE_LIST = new ConcurrentHashSet<String>();
/**
* 文件类型黑名单
*/
private final static Set<String> FILE_NAME_BLACK_LIST = new ConcurrentHashSet<String>();
/**
* 文件类型大小
*/
private final static Map<String, Long> FILE_TYPE_SIZE = new ConcurrentHashMap<String, Long>();
/**
* 需要过滤内容的类型
*/
private final static Set<String> CONTENT_FILTER_FILE_TYPE = new ConcurrentHashSet<String>();
/**
* 内容过滤的字符
*/
private final static Set<String> CONTENT_COMMAND = new ConcurrentHashSet<String>();
/**
* 文件头信息对应的文件类型
*/
private final static Map<String, String> FILE_TYPE_HEAD = new ConcurrentHashMap<String, String>();
static {
//白名单
FILE_NAME_WHITE_LIST.add(".txt");
FILE_NAME_WHITE_LIST.add(".xlsx");
FILE_NAME_WHITE_LIST.add(".rar");
FILE_NAME_WHITE_LIST.add(".jpg");
FILE_NAME_WHITE_LIST.add(".gif");
FILE_NAME_WHITE_LIST.add(".jpeg");
FILE_NAME_WHITE_LIST.add(".png");
FILE_NAME_WHITE_LIST.add(".mp4");
FILE_NAME_WHITE_LIST.add(".zip");
FILE_NAME_WHITE_LIST.add(".et");
FILE_NAME_WHITE_LIST.add(".csv");
FILE_NAME_WHITE_LIST.add(".xls");
FILE_NAME_WHITE_LIST.add(".tms");
FILE_NAME_WHITE_LIST.add(".mid");
FILE_NAME_WHITE_LIST.add(".midi");
FILE_NAME_WHITE_LIST.add(".amr");
//黑名单
FILE_NAME_BLACK_LIST.add(".exe");
FILE_NAME_BLACK_LIST.add(".bat");
FILE_NAME_BLACK_LIST.add(".sh");
FILE_NAME_BLACK_LIST.add(".jar");
FILE_NAME_BLACK_LIST.add(".jsp");
FILE_NAME_BLACK_LIST.add(".php");
FILE_NAME_BLACK_LIST.add(".asp");
FILE_NAME_BLACK_LIST.add(".js");
//文件类型的大小
FILE_TYPE_SIZE.put(".jpg", 20 * 1024 * 1024L);
FILE_TYPE_SIZE.put(".gif", 20 * 1024 * 1024L);
FILE_TYPE_SIZE.put(".jpeg", 20 * 1024 * 1024L);
FILE_TYPE_SIZE.put(".png", 20 * 1024 * 1024L);
FILE_TYPE_SIZE.put(".mp4", 20 * 1024 * 1024L);
//需要过滤内容的类型
CONTENT_FILTER_FILE_TYPE.add(".jpg");
CONTENT_FILTER_FILE_TYPE.add(".gif");
CONTENT_FILTER_FILE_TYPE.add(".jpeg");
CONTENT_FILTER_FILE_TYPE.add(".png");
CONTENT_FILTER_FILE_TYPE.add(".mp4");
//内容过滤的字符
CONTENT_COMMAND.add("cmd");
CONTENT_COMMAND.add("exec");
CONTENT_COMMAND.add("spy");
CONTENT_COMMAND.add("shell");
CONTENT_COMMAND.add("execute");
CONTENT_COMMAND.add("system");
CONTENT_COMMAND.add("command");
//文件头信息对应的文件类型
FILE_TYPE_HEAD.put(".jpg","ffd8ffe0");
FILE_TYPE_HEAD.put(".jpeg","ffd8ffe0");
FILE_TYPE_HEAD.put(".png","89504E47");
FILE_TYPE_HEAD.put(".gif","47494638");
}
private UploadFileCheckUtil() {
}
public static void fileCheck(FileItem file) {
if (file == null || file.isFormField()) {
return;
}
String name = file.getName();
if (name == null || name.trim().length() == 0) {
throw new RuntimeException("Failed to obtain file name");
}
whiteBlackListCheck(name);
String fileType = name.substring(name.lastIndexOf("."));
fileSizeCheck(fileType, file.getSize());
if (isFilterContent(fileType)) {
filterContent(new String(file.get()));
}
}
public static void fileCheck(String fileName, String content) {
if (fileName == null || fileName.trim().length() == 0) {
throw new RuntimeException("Failed to obtain file name");
}
whiteBlackListCheck(fileName);
String fileType = fileName.substring(fileName.lastIndexOf("."));
fileSizeCheck(fileType, content.length());
if (isFilterContent(fileType)) {
filterContent(content);
}
}
public static void fileSizeCheck(String fileType, long fileSize) {
Long maxSize = FILE_TYPE_SIZE.get(fileType.toLowerCase());
if (maxSize != null && fileSize > maxSize) {
throw new RuntimeException("The file size is too long, fileSize:" + fileSize + ",maxSize:" + maxSize);
}
}
public static void whiteBlackListCheck(String name) {
whiteListCheck(name);
blackListCheck(name);
}
public static void whiteListCheck(String name) {
for (String whiteList : FILE_NAME_WHITE_LIST) {
if (name.toLowerCase().endsWith(whiteList)) {
return;
}
}
throw new RuntimeException("The file name is not in the whitelist, fileName:" + name);
}
public static void blackListCheck(String name) {
for (String blackList : FILE_NAME_BLACK_LIST) {
if (name.toLowerCase().contains(blackList)) {
throw new RuntimeException("The file name is in the blacklist, fileName:" + name);
}
}
}
public static boolean isFilterContent(String fileType) {
for (String type : CONTENT_FILTER_FILE_TYPE) {
if (fileType.toLowerCase().contains(type)) {
return true;
}
}
return false;
}
/**
* 过滤内容中是否有命令字符
* @param content 内容
*/
public static void filterContent(String content) {
String lowerCase = content.toLowerCase();
for (String command : CONTENT_COMMAND) {
String quotesCommand = "'" + command + "'";
String blankCommand = " " + command + " ";
if (lowerCase.startsWith(command) || lowerCase.endsWith(command) ||
lowerCase.contains("'" + command) || lowerCase.contains(" " + command) ||
lowerCase.contains(quotesCommand) || lowerCase.contains(blankCommand)) {
throw new RuntimeException("Content includes commands, commands:" + command);
}
}
}
public static void headCheck(String fileType, String head) {
if (fileType == null || fileType.trim().length() == 0 || head == null || head.trim().length() == 0) {
throw new RuntimeException("Failed to get the file type or file header");
}
if (FILE_TYPE_HEAD.containsKey(fileType.toLowerCase())) {
if (!head.toLowerCase().equals(FILE_TYPE_HEAD.get(fileType))) {
throw new RuntimeException("Type that does not match, fileType:" + fileType);
}
}
}
/**
* 新增白名单文件类型
* @param fileType 文件类型
*/
public static void addWhiteList(String fileType) {
FILE_NAME_WHITE_LIST.add(fileType);
}
/**
* 新增黑名单文件类型
* @param fileType 文件类型
*/
public static void addBlackList(String fileType) {
FILE_NAME_BLACK_LIST.add(fileType);
}
/**
* 新增需要过滤内容的文件类型
* @param fileType 文件类型
*/
public static void addFilterContentFileType(String fileType) {
CONTENT_FILTER_FILE_TYPE.add(fileType);
}
/**
* 新增过滤文件内容
* @param command 需要过滤的文件内容
*/
public static void addFilterContent(String command) {
CONTENT_COMMAND.add(command);
}
/**
* 移除白名单文件类型
* @param fileType 文件类型
*/
public static void removeWhiteList(String fileType) {
FILE_NAME_WHITE_LIST.remove(fileType);
}
/**
* 移除黑名单文件类型
* @param fileType 文件类型
*/
public static void removeBlackList(String fileType) {
FILE_NAME_BLACK_LIST.remove(fileType);
}
/**
* 移除需要过滤内容的文件类型
* @param fileType 文件类型
*/
public static void removeFilterContentFileType(String fileType) {
CONTENT_FILTER_FILE_TYPE.remove(fileType);
}
/**
* 移除过滤文件内容
* @param command 需要过滤的文件内容
*/
public static void removeFilterContent(String command) {
CONTENT_COMMAND.remove(command);
}
}