问题
之前在《Xss过滤器(Java)》使用OWASP库了,不过当时只能防止一般rest接口,对于上传流就有点搞不定了。这里继续使用OWASP库来防止xss注入。
步骤
Maven
<properties>
<esapi.version>2.5.1.0</esapi.version>
</properties>
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
<version>${esapi.version}</version>
</dependency>
Spring
过滤器
ReplaceRequestBodyFilter.java
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class ReplaceRequestBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String contentType = request.getContentType();
if (null != contentType && contentType.contains("multipart/")) {//说明是文件上传
XSSCommonsMultipartResolver commonsMultipartResolver = new XSSCommonsMultipartResolver();
XSSMultipartHttpServletRequest resolveMultipart = (XSSMultipartHttpServletRequest) commonsMultipartResolver.resolveMultipart(request);
filterChain.doFilter(resolveMultipart, response);
} else {
filterChain.doFilter(request, response);
}
}
}
XSSCommonsMultipartResolver.java
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import javax.servlet.http.HttpServletRequest;
public class XSSCommonsMultipartResolver extends CommonsMultipartResolver {
private boolean resolveLazily = false;
public XSSCommonsMultipartResolver() {
super();
}
@Override
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new XSSMultipartHttpServletRequest(request) {
@Override
protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
}
};
} else {
MultipartParsingResult parsingResult = parseRequest(request);
return new XSSMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}
}
}
XSSMultipartHttpServletRequest.java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.owasp.esapi.ESAPI;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@Slf4j
public class XSSMultipartHttpServletRequest extends DefaultMultipartHttpServletRequest {
public XSSMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {
super(request);
setMultipartFiles(mpFiles);
setMultipartParameters(mpParams);
setMultipartParameterContentTypes(mpParamContentTypes);
}
public XSSMultipartHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
MultiValueMap<String, MultipartFile> result = super.getMultipartFiles();
Set<String> keySet = result.keySet();
for (String key : keySet) {
List<MultipartFile> fileList = result.get(key);
List<MultipartFile> multipartFiles = new ArrayList<>();
for (MultipartFile file : fileList) {
if (file!= null && file.getOriginalFilename()!= null) {
String fileName = file.getOriginalFilename();
try {
InputStream inputStream = file.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
FileItem fileItem = new DiskFileItemFactory().createItem("file", file.getContentType(), false, fileName);
OutputStream os = fileItem.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
while (bufferedReader.ready()){
# 从请求流中读取一行
String line = bufferedReader.readLine();
# xss过滤一行后,并写入新的构建的请求对象中
writer.write(stripXSS(line));
writer.newLine();
}
writer.flush();
CommonsMultipartFile commonsMultipartFile = new CommonsMultipartFile(fileItem);
multipartFiles.add(commonsMultipartFile);
} catch (IOException e) {
log.error("修改请求流时错误:", e);
}
}
}
result.put(key, multipartFiles);
}
return result;
}
private String stripXSS(String value) {
if (value != null) {
// NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to
// avoid encoded attacks.
value = ESAPI.encoder().canonicalize(value);
// Avoid null characters
value = value.replaceAll("", "");
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything between pdf script tags
scriptPattern = Pattern.compile("js", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("javascript", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a src='...' type of expression
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome </script> tag
scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome <script ...> tag
scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid eval(...) expressions
scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid expression(...) expressions
scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid javascript:... expressions
scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid vbscript:... expressions
scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid οnlοad= expressions
scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
}
return value;
}
}
这个类,才是防xss的关键。
总结
到这里上传文件流防xss攻击,只需要3个类。主要是利用Spring过滤器机制来覆盖掉请求体对象。注意,一定要新建一个对象进行覆盖,而且不能关流。
参考:
|