公司最近在搞測試,請的第三方測試機構。。。一頓操作猛如虎。。。
記錄一次現在很多項目都容易忽略的存儲型跨站腳本漏洞處理,就是XSS攻擊漏洞,只要有用戶輸入的地方,就可能會有XSS漏洞,比如我們在留言板,或者發佈文章的地方輸入:
- <script>alert(1);</script>
當這條數據被存儲到數據庫,並且在頁面上再次顯示的時候,就會彈窗輸出“1”,這只是簡單的script腳本,嚴重的可能會出現reload到釣魚網站,讓用戶輸入密碼、sql注入,獲取數據或者刪庫、獲取當前的cookie,並將數據發送到指定郵箱等問題,後果不堪設想。
很多網站爲了避免XSS的攻擊,對用戶的輸入都採取了過濾,最常見的就是對<>轉換,轉換成大於號,小於號,驗證了一下CSDN的留言板,是將<script>轉換成[removed],經過轉換以後<>雖然可在正確顯示在頁面上,但是已經不能構成代碼語句了。
我們的項目是用的SpringMvc框架,所以就記錄在SpringMvc項目中如何解決XSS攻擊問題:
主要邏輯:
- 建一個過濾器,過濾來自頁面的請求
- 在過濾器中,將原本的request替換成我們自己包裝好的request
- 我們寫一個request的裝飾類,裏面重寫獲取參數的方法,對參數進行轉義和替換
具體代碼:
-
reqeust包裝類,重寫getParameter()等方法,這裏面對參數的轉義和替換,根據實際需求更改
package com.study.util; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.regex.Pattern; /** * @Auther: 安靜讀書 * @Date: 2019/9/26 09:34 * @Description:重寫getParameter方法 */ public class XssRequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest request; public XssRequestWrapper(HttpServletRequest request) { super(request); this.request = request; } /** * 重寫getParameter方法 */ @Override public String getParameter(String name) { String value = super.getParameter(name); if (value == null) { return null; } value = format(value); return value; } /** * 重寫getParameterMap */ @Override @SuppressWarnings("unchecked") public Map<String, String[]> getParameterMap() { HashMap<String, String[]> paramMap = new HashMap<>(super.getParameterMap()); paramMap = (HashMap<String, String[]>) paramMap.clone(); for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry<String, String[]> entry = (Map.Entry<String, String[]>) iterator.next(); String [] values = entry.getValue(); for (int i = 0; i < values.length; i++) { if(values[i] instanceof String){ values[i] = format(values[i]); } } entry.setValue(values); } return paramMap; } /** * 重寫getParameterValues */ @Override public String[] getParameterValues(String name) { String[] encodedValues = null; String[] values = super.getParameterValues(name); if(values != null){ int count = values.length; encodedValues = new String[count]; for (int i = 0; i < count; i++) { encodedValues[i] = format(values[i]); } } return encodedValues; } /** * 重寫getHeader */ @Override public String getHeader(String name) { return format(super.getHeader(name)); } public String filter(String message) { if (message == null) return (null); message = format(message); return message; } /** * @desc 統一處理特殊字符的方法,替換掉sql和js的特殊字符 * @param name 要替換的字符 */ private String format(String name) { return xssEncode(name); } /** * 將容易引起xss & sql漏洞的半角字符直接替換成全角字符 * * @param s * @return */ private static String xssEncode(String s) { if (s == null || s.isEmpty()) { return s; }else{ s = stripXSSAndSql(s); } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '>': sb.append(">");// 轉義大於號 break; case '<': sb.append("<");// 轉義小於號 break; // case '\'': // sb.append("'");// 轉義單引號 // break; // case '\"': // sb.append(""");// 轉義雙引號 // break; // case '&': // sb.append("&");// 轉義& // break; // case '#': // sb.append("#");// 轉義# // break; default: sb.append(c); break; } } return sb.toString(); } /** * 防止xss跨腳本攻擊(替換,根據實際情況調整) */ public static String stripXSSAndSql(String value) { if (value != null) { Pattern scriptPattern = Pattern.compile("<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome </script> tag scriptPattern = Pattern.compile("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome <script ...> tag scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid eval(...) expressions scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid e-xpression(...) expressions scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid javascript:... expressions scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid vbscript:... expressions scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid onload= expressions scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); } return value; } }
-
過濾器類
package com.study.util; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Auther: 安靜讀書 * @Date: 2019/9/26 09:33 * @Description: 處理xss攻擊的過濾器 */ public class RequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 將request通過自定義的裝飾類進行裝飾 com.study.om.util.XssRequestWrapper xssRequest = new com.study.om.util.XssRequestWrapper((HttpServletRequest) request); filterChain.doFilter(xssRequest, response); } }
-
在web.xml中加入自己的過濾器,可以加到filter後面
<!-- xss注入過濾器 --> <filter> <filter-name>xssRequestFilter</filter-name> <filter-class>com.study.util.RequestFilter</filter-class> </filter> <filter-mapping> <filter-name>xssRequestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
當然,如果項目不是SpringMVC的模式,而且輸入的地方很少,也可以加一層簡單的過濾工具類:
package com.study.util;
import java.util.regex.Pattern;
/**
* @Auther: 安靜讀書
* @Date: 2019/9/26 14:17
* @Description:解決存儲型跨站腳本漏洞,過濾特殊字符
*/
public class XSSUtil {
public static String striptXSS(String value) {
if (value != null) {
value = value.replaceAll("", "");
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
scriptPattern = Pattern.compile("<script(.*?)>", 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(".*<.*", Pattern.CASE_INSENSITIVE );
value = scriptPattern.matcher(value).replaceAll("");
}
return value;
}
}