java 攔截器解決xss攻擊
一、xss攻擊
XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令程式碼到網頁,使用戶載入並執行攻擊者惡意製造的網頁程式。這些惡意網頁程式通常是JavaScript,但實際上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻擊成功後,攻擊者可能得到包括但不限於更高的許可權(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。
簡單說就是說,通過在輸入框輸入一些js程式碼,如在帳號密碼輸入框中輸入
<video src=1 onerror=alert(/xss/)/>
或者
<script>alert("@@") </script>
這樣點擊提交的時候就會觸發alert彈窗,分別彈出 xss 和 @@ 的內容,這裡只是做個簡單的演示,彈了個窗口,還能存儲病毒下載地址到服務端,進入的時候自動下載,或者修改你的cookie啥的,這裡感興趣可以百度查查xss攻擊。
二、如何防禦
解決思路對用戶提交表單的參數進行轉移,如把< 轉換為 < 把 > 轉換為 &rt;
java有很多的過濾工具類
<dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
然後通過下面的程式碼即可過濾
StringEscapeUtils.escapeHtml(string);
底層也是將一切標籤進行轉移,達到js調用不生效的作用。
這裡使用的是filter過請求進行攔截處理。
過濾的內容報過get請求的參數、對象, post形式body中的參數
1)添加xss過濾器
<!-- xss過濾器 --> <filter> <filter-name>XssgFilter</filter-name> <filter-class>com.train.web.filter.XssFilter</filter-class> </filter> <filter-mapping> <filter-name>XssgFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
這裡過濾了所有的請求,其中XssFilter是我們自己過濾器
2)添加自己的過濾器,
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { XssHttpServletRequestWrapper req=new XssHttpServletRequestWrapper((HttpServletRequest)servletRequest); filterChain.doFilter(req,servletResponse); } @Override public void destroy() { } }
3)定義自己的http包裝類
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { boolean isUpData = false;//判斷是否是上傳 上傳忽略 public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) { super(servletRequest); String contentType = servletRequest.getContentType (); if (null != contentType) isUpData =contentType.startsWith ("multipart"); } @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] = cleanXSS(values[i]); } return encodedValues; } @Override public String getParameter(String parameter) { String value = super.getParameter(parameter); if (value == null) { return null; } return cleanXSS(value); } /** * 獲取request的屬性時,做xss過濾 */ @Override public Object getAttribute(String name) { Object value = super.getAttribute(name); if (null != value && value instanceof String) { value = cleanXSS((String) value); } return value; } @Override public String getHeader(String name) { String value = super.getHeader(name); if (value == null) return null; return cleanXSS(value); } 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("'", "'"); value = value.replaceAll("eval\\((.*)\\)", ""); value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); value = value.replaceAll("script", ""); return value; } @Override public ServletInputStream getInputStream () throws IOException { if (isUpData){ return super.getInputStream (); }else{ 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(); } } } return cleanXSS(sb.toString ()); } }
但是這裡有個問題,假如這裡還有一些特殊的需求,有些html標籤是希望在前端能顯示的,前端通過一些已經防止了xss攻擊的富文本控制項輸入資訊,後台不希望將這些資訊轉義
三、添加一些額外的內容
1)希望能動態的開關
2)期待部分介面的介面的參數是能存在標籤的
添加一個xss開關的控制類, 這裡使用了配置中心
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class XSSFilterConfigUtil { public static Boolean openXssProtect; public static Boolean getOpenXssProtect() { return openXssProtect == null ? false : openXssProtect; } @Value("${open.xss.protect}") public void setOpenXssProtect(Boolean openXssProtect) { XSSFilterConfigUtil.openXssProtect = openXssProtect; } }
注意的是:
1. @Value無法為靜態屬性注入值,所以需要添加set方法為其注入值;
2. 工具類必須添加@Component或者@Service註解,否則@Value不起作用。
靜態方法中注入了值以後,Filter中就可以直接使用了。
修改上面的http包裝類,這裡不對” 進行過濾,過濾的話,會把json的””個去除,使用@RequestBody沒辦法解析成為一個正常的對象
import com.alibaba.fastjson.JSONObject; import com.train.service.impl.XSSFilterConfigUtil; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 防護http處理 * 1.過濾xss */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final Logger LOGGER = LoggerFactory.getLogger(XssHttpServletRequestWrapper.class); boolean isUpData = false;//判斷是否是上傳 上傳忽略 //不期待被過濾的的鏈接和欄位(管理後台使用了富文本,希望有可編輯的內容) HashMap<String, String> doNotFilterURLAndParamMap = new HashMap<String, String>() { { put("/api/v2/group/manage", "description"); put("/api/v1/sendNews", "content"); } }; /** * Constructs a request object wrapping the given request. * * @param request The request to wrap * @throws IllegalArgumentException if the request is null */ public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); String contentType = request.getContentType (); if (null != contentType) isUpData =contentType.startsWith ("multipart"); } /** * 過濾單個參數 * @param name * @return */ @Override public String getParameter(String name) { String parameter = super.getParameter(name); if(StringUtils.isNotBlank(parameter) && XSSFilterConfigUtil.getOpenXssProtect()){ //這裡使用的阿帕奇的common-lang3中的轉義html方法,也可以自己實現, String escapeParameter = this.cleanXSS(parameter); return escapeParameter; } return parameter; } /** * 過濾實體的每個參數 * @param name * @return */ @Override public String[] getParameterValues(String name) { String[] parameterValues = super.getParameterValues(name); if (parameterValues == null) { return null; } if (XSSFilterConfigUtil.getOpenXssProtect()) { for (int i = 0; i < parameterValues.length; ++i) { String value = parameterValues[i]; parameterValues[i] = this.cleanXSS(value); } } return parameterValues; } /** * 處理@RequestBody的形式傳入的json數據 * @return * @throws IOException */ @Override public ServletInputStream getInputStream () throws IOException { if(!XSSFilterConfigUtil.getOpenXssProtect()) { return super.getInputStream (); } if (isUpData){ return super.getInputStream (); }else{ final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ()); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } }; } } 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(); } } } String requestUrl = StringUtils.replaceOnce(this.getRequestURI(), this.getContextPath(), StringUtils.EMPTY); boolean needFilter = false; String key = ""; String param = ""; for(Map.Entry<String, String> entry : doNotFilterURLAndParamMap.entrySet()){ key = entry.getKey(); int index = StringUtils.indexOf(key, "*"); if (index > 0) { String[] array = key.split("\\*"); StringBuffer stringBuffer = new StringBuffer(); for (String s : array) { stringBuffer.append(s).append("(.*)"); } Pattern p = Pattern.compile(stringBuffer.toString()); Matcher m = p.matcher(requestUrl); if (m.find()) { needFilter = true; param = entry.getValue(); break; } } else { if (requestUrl.equals(key)) { needFilter = true; param = entry.getValue(); break; } } } if(needFilter) { //有需要特殊處理的欄位,不希望過濾標籤 try { /*String param = doNotFilterURLAndParamMap.get(requestUrl);*/ JSONObject jsonObject = JSONObject.parseObject(sb.toString()); if(jsonObject.containsKey(param)) { Object notFilterValue = jsonObject.get(param); String cleanXSSParams = cleanXSS(sb.toString ()); JSONObject filteredJson = JSONObject.parseObject(cleanXSSParams); filteredJson.put(param, notFilterValue); return filteredJson.toJSONString(); }else { return cleanXSS(sb.toString ()); } }catch (Exception e) { LOGGER.error("XssHttpServletRequestWrapper轉換json數據失敗",e); return cleanXSS(sb.toString ()); //異常時,就直接過濾,不管需要特殊處理的參數 } }else { return cleanXSS(sb.toString ()); } } /** * 過濾規則,這裡不直接使用StringEscapeUtils.escapeHtml,因為獲取的是一個json字元串,會將" 替換導致數據異常,沒有""進行分割,無法正常注入到@RequestBody * @param value * @return */ 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("'", "'"); /* value = value.replaceAll("eval\\((.*)\\)", ""); value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); value = value.replaceAll("script", "");*/ return value; } }
感謝你的閱讀,接外包、也找兼職