Spring Boot 利用Filter 實現防止XSS攻擊+設定Cookie HttpOnly
阿新 • • 發佈:2020-07-28
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;
}
}