Spring Boot 利用Filter 实现防止XSS攻击+设置Cookie HttpOnly
介绍
跨站脚本攻击(XSS),是目前最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。
思路
了解了很多在Spring环境下对XSS攻击的处理,主要思路是重写request获取数据的方法,在过滤器上使用新方法,过滤数据,达到防止XSS攻击的效果。(没有用到SpringSecurity)
简单来说就是创建一个新的HttpRequest类XssHttpServletRequestWrapper,然后重写一些get方法(获取参数时对参数进行XSS判断预防),这里的处理是将特殊字符进行转换。
环境
- SpringBoot
- JDK 1.8
处理方法
将参数中的特殊字符进行转换
- 输入:
<script>alert(1);</script>
- 处理后为:
<script>alert(1);</script>
后台处理
- 创建XssAndSqlHttpServletRequestWrapper
继承HttpServletRequestWrapper,重写方法getParameter、getParameterValues、getInputStream方法,处理请求参数
package com.repository.core.xss;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* @description: 自定义类继承HttpServletRequestWrapper
* @date: 2020/7/21 15:32
* @version: 1.0
* @return
*/
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static Logger logger = LoggerFactory.getLogger(XssAndSqlHttpServletRequestWrapper.class);
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
return StringEscapeUtils.escapeHtml4(super.getHeader(name));
}
@Override
public String getQueryString() {
String rawStr = super.getQueryString();
if (StringUtils.isNotBlank(rawStr)) {
return StringEscapeUtils.escapeHtml4(rawStr);
}
return null;
}
@Override
public String getParameter(String name) {
String filterName = StringEscapeUtils.escapeHtml4(super.getParameter(name));
if (SafeFilterUtil.XssCheck(name)) {
logger.info("非法字符串!【" + name + "】");
logger.info("转换后:【" + filterName + "】");
return filterName;
} else {
return filterName;
}
}
@Override
public String[] getParameterValues(String name) {
String[] parameterValues = super.getParameterValues(name);
if (parameterValues == null) {
return null;
}
for (int i = 0; i < parameterValues.length; i++) {
String value = parameterValues[i];
if (SafeFilterUtil.XssCheck(value)) {
logger.info("非法字符串!【" + value + "】");
parameterValues[i] = StringEscapeUtils.escapeHtml4(value);
logger.info("转换后:【" + parameterValues[i] + "】");
} else {
parameterValues[i] = StringEscapeUtils.escapeHtml4(value);
}
}
return parameterValues;
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream()).getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
public String inputHandlers(ServletInputStream servletInputStream) {
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (servletInputStream != null) {
try {
servletInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// logger.info("非法字符串!【" + sb.toString() + "】");
// logger.info("转换后:【" + cleanXSS(sb.toString()) + "】");
return cleanXSS(sb.toString());
}
private static String cleanXSS(String value) {
value = value.replaceAll("<", "").replaceAll(">", "");
value = value.replaceAll("%3C", "").replaceAll("%3E", "");
value = value.replaceAll("\\(", "").replaceAll("\\)", "");
value = value.replaceAll("%28", "").replaceAll("%29", "");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "");
value = value.replaceAll("script", "");
return value;
}
}
注意:
getInputStream()方法的流处理,注解方式获取数据貌似是根据这个流取得的数据。
因为super.getInputStream()流只允许读取一次,所以在getInputStream()方法中
处理完流数据后返回了一个新的ServletInputStream。另外替换方法里的替换规则,
也可以根据实际业务需要进行调整。
- 创建过滤器 CookieFilter
package com.repository.core.xss;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CookieFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(CookieFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("===cookie filter init...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
Cookie[] cookies = req.getCookies();
logger.info("cookie filter start");
if (cookies != null) {
Cookie cookie = cookies[0];
if (cookie != null) {
String value = cookie.getValue();
StringBuilder builder = new StringBuilder();
builder.append("JSESSIONID=" + value + "; ");
builder.append("path=/;");
builder.append("Secure; ");//HTTPS协议下应启用Secure属性
builder.append("HttpOnly; ");//将Cookie设置为HttpOnly,防止被脚本获取
resp.setHeader("Set-Cookie", builder.toString());
}
}
logger.info("cookie filter end...");
filterChain.doFilter(servletRequest, resp);
}
@Override
public void destroy() {
logger.info("===cookie filter complete...");
}
}
- 创建过滤器 XssFilter
package com.repository.core.xss;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @description: 实现自己的XssFilter
* @date: 2020/7/21 15:32
* @version: 1.0
* @return
*/
//@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
public class XssFilter implements Filter {
Logger logger = LoggerFactory.getLogger(XssFilter.class);
FilterConfig filterConfig = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void destroy() {
this.filterConfig = null;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("XSS filter start");
HttpServletRequest req = (HttpServletRequest) request;
XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req);
logger.info("XSS filter end...");
chain.doFilter(xssRequestWrapper, response);
}
}
4.添加过滤器
-
方式1-使用注解
-
- 在程序入口添加注解 @ServletComponentScan
-
- 在过滤器上添加注解 @WebFilter(filterName="myXssFilter", urlPatterns="/*")
-
方式2-使用JavaBean
-
- 创建配置类
package com.repository.config;
import com.repository.core.xss.CookieFilter;
import com.repository.core.xss.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* description: 拦截器配置类
* date: 2020/7/24 13:47
* version: 1.0
*/
@Configuration
public class ComponentFilterOrderConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean1() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setName("XssFilter");
filterRegistrationBean.setOrder(2);//order的数值越小 则优先级越高
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean2() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new CookieFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setName("CookieFilter");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
}