背景:过滤xss攻击,同时将过滤后的日志输出到指定文件。(指定文件输出请看上篇博文)
前景:利用filter进行xss攻击过滤,需要应对不同请求做不同的过滤处理,若是post请求的json格式数据,需要重写getinputstream方法(因为流读取一次后,下层controller无法再次进行读取。原理可自行百度)
因此需要重写两个wrapper(继承HttpServletRequestWrapper)(针对post的json请求以及其他格式的请求)
在filter中以contentType做类型的区分
未做业务耦合的区分(日志输出与xss过滤耦合到一起),若要区分开功能,单独书写即可
一:重写wrapper
1:contentType为multipart/form-data或application/x-www-form-urlencoded
public class XSSNormalRequestWrapper extends HttpServletRequestWrapper {
public XSSNormalRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = stripXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return stripXSS(value);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return stripXSS(value);
}
}
2:contentType为application/json
public class XSSBodyRequestWrapper extends HttpServletRequestWrapper {
private String body;
public XSSBodyRequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
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) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = XSSStripUtils.stripXSS(stringBuilder.toString());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
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 body;
}
}
二:xss处理工具方法:
public class XSSStripUtils {
public static String stripXSS(String value) {
if (value != null) {
Pattern scriptPattern = Pattern.compile("<script>(\\s*.*?)</script>",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("</script(\\s*.*?)>",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("<script(\\s*.*?)>",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("eval\\((.*?)\\)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("expression\\((.*?)\\)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("javascript:",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("vbscript:",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("onload(.*?)=",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("-");
scriptPattern = Pattern.compile("<+.*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onerror|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+.*=+",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("-");
// 过滤emoji表情
scriptPattern = Pattern
.compile(
"[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]",
Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("-");
}
return value;
}
}
三:filter过滤器
public class XSSFilter implements Filter{
private static final Logger logger = LoggerFactory.getLogger("external_request");
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
resetCookies(request,(HttpServletResponse)response);
String contentType = request.getContentType();
if (StringUtils.isNotBlank(contentType) && contentType.contains("multipart/form-data")) {
MultipartHttpServletRequest multipartHttpServletRequest =
new CommonsMultipartResolver().resolveMultipart(request);
XSSNormalRequestWrapper xssNormalRequestWrapper = new XSSNormalRequestWrapper(multipartHttpServletRequest);
logUrlAndParam(request,xssNormalRequestWrapper,null);
chain.doFilter(xssNormalRequestWrapper, response);
} else if (StringUtils.isNotBlank(contentType) && contentType.contains("application/x-www-form-urlencoded")) {
XSSNormalRequestWrapper xssNormalRequestWrapper = new XSSNormalRequestWrapper(request);
logUrlAndParam(request,xssNormalRequestWrapper,null);
chain.doFilter(xssNormalRequestWrapper, response);
} else if (StringUtils.isNotBlank(contentType) && contentType.contains("application/json")) {
XSSBodyRequestWrapper xssBodyRequestWrapper = new XSSBodyRequestWrapper(request);
logUrlAndParam(request,null,xssBodyRequestWrapper);
chain.doFilter(xssBodyRequestWrapper, response);
} else {
logUrlAndParam(request,null,null);
chain.doFilter(request, response);
}
}
public Cookie[] resetCookies(HttpServletRequest request,
HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
Map<String, String> map = new HashMap<>();
if (cookies != null && cookies.length != 0) {
for (Cookie cookie : cookies) {
String value = cookie.getValue();
if (value != null) {
String deleteValue = stripXSS(value);
if (!value.equals(deleteValue)) {
map.put(cookie.getName(), cookie.getDomain());
}
}
}
if (CollectionUtils.isNotEmpty(map)) {
for (Map.Entry<String, String> cookie : map.entrySet()) {
CookieUtil.removeCookie(request, response, cookie
.getValue(), cookie.getKey());
}
}
}
return cookies;
}
@Override
public void destroy() {
}
private void logUrlAndParam(HttpServletRequest request, XSSNormalRequestWrapper xssNormalRequestWrapper,XSSBodyRequestWrapper xssBodyRequestWrapper) {
// 获取请求URL
String url = request.getRequestURI();
// 获取请求方法
String method = request.getMethod();
// 获取path
String path = request.getQueryString();
// 获取header
Map<String, String> map = new HashMap<String, String>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {//循环遍历Header中的参数,把遍历出来的参数放入Map中
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
String header = JSON.toJSONString(map);
// 获取参数
String params = "";
if(xssNormalRequestWrapper != null) {
params = JSON.toJSONString(xssNormalRequestWrapper.getParameterMap());
}
if(xssBodyRequestWrapper != null) {
params = xssBodyRequestWrapper.getBody();
}
if(xssNormalRequestWrapper == null && xssBodyRequestWrapper == null) {
params = JSON.toJSONString(request.getParameterMap());
}
logger.info("请求url:{},方法:{},path:{},header:{},param:{}",url,method,path,header,params);
}
}
四:在web.xml配置过滤器
<filter>
<filter-name>XSSFilter</filter-name>
<filter-class>xxxx.filter.XSSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XSSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>