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;
}
}