spring boot 使用Filter 拦截XSS
目标
使用spring boot 的 Filter 对XSS进行拦截
工具
- spring boot 2.0
- jsoup (可选)
实现原理
spring boot 的 Filter 拦截到前端的参数后进行过滤(看着是不是很简单??)。
说白了就是两个功能:参数拦截、脚本过滤。
参数拦截
想要过滤XSS首先要能拦截到前端的参数。
先写个Filter:
1import java.io.IOException;
2
3import javax.servlet.Filter;
4import javax.servlet.FilterChain;
5import javax.servlet.FilterConfig;
6import javax.servlet.ServletException;
7import javax.servlet.ServletRequest;
8import javax.servlet.ServletResponse;
9import javax.servlet.http.HttpServletRequest;
10
11public class XSSEscapeFilter implements Filter {
12
13
14 @Override
15 public void init(FilterConfig filterConfig) throws ServletException {
16
17 }
18
19 @Override
20 public void destroy() {
21
22 }
23
24 @Override
25 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
26 //后面会有 XssHttpServletRequestWrapper 的代码。这个类是自己定义的
27 chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
28 }
29}
30
这个Filter 是可以拦截到请求的,但是呢,如果想要对参数进行修改就需要重新定义 HttpServletRequestWrapper,只有用自定义的HttpServletRequestWrapper 才能对参数进行修改。
下面定义 XssHttpServletRequestWrapper:
1
2import org.apache.commons.lang3.StringUtils;
3import org.jsoup.Jsoup;
4import org.jsoup.nodes.Document;
5import org.jsoup.safety.Whitelist;
6
7import javax.servlet.ReadListener;
8import javax.servlet.ServletInputStream;
9import javax.servlet.http.HttpServletRequest;
10import javax.servlet.http.HttpServletRequestWrapper;
11import java.io.*;
12import java.util.HashMap;
13import java.util.Iterator;
14import java.util.Map;
15
16/**
17 * 实现XSS过滤
18 * Create by zdRan on 2018/5/8
19 *
20 * @author cm.zdran@gmail.com
21 */
22public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
23 private HttpServletRequest orgRequest = null;
24
25 public XssHttpServletRequestWrapper(HttpServletRequest request) {
26 super(request);
27 orgRequest = request;
28
29 }
30
31 @Override
32 public String getParameter(String name) {
33 // 对参数进行修改
34 return name;
35 }
36
37 @Override
38 public Map getParameterMap() {
39 // 对参数进行修改
40 return super.getParameterMap();;
41 }
42
43 @Override
44 public String[] getParameterValues(String name) {
45 String[] arr = super.getParameterValues(name);
46 // 对参数进行修改
47 return arr;
48 }
49
50 @Override
51 public String getHeader(String name) {
52 //对参数进行修改
53 return super.getHeader(name);;
54 }
55
56 /**
57 * 获取最原始的request
58 *
59 * @return
60 */
61 public HttpServletRequest getOrgRequest() {
62 return orgRequest;
63 }
64
65 /**
66 * 获取最原始的request的静态方法
67 *
68 * @return
69 */
70 public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
71 if (req instanceof XssHttpServletRequestWrapper) {
72 return ((XssHttpServletRequestWrapper) req).getOrgRequest();
73 }
74
75 return req;
76 }
这样就能对参数进行修改了,但是,目前的情况还不能处理POST请求,或者 RequestBody 注解。
当使用 RequestBody 注解时,你会发现,重写的这几个方法都没有走,说明我们没有重写全方法。
找了一些资料发现:RequestBody注解读取参数的方法是getInputStream() 。
我们重写一下这个方法:
1 @Override
2 public ServletInputStream getInputStream() throws IOException {
3
4 BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream()));
5 String line = br.readLine();
6 String result = "";
7 if (line != null) {
8 //对参数进行处理
9 }
10
11 return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes()));
12 }
然后启动这个 Filter
1
2import org.springframework.boot.web.servlet.FilterRegistrationBean;
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5
6import javax.servlet.DispatcherType;
7
8/**
9 * Create by zdRan on 2018/5/8
10 *
11 * @author cm.zdran@gmail.com
12 */
13@Configuration
14public class XssFilterConfiguration {
15 /**
16 * xss过滤拦截器
17 */
18 @Bean
19 public FilterRegistrationBean xssFilterRegistrationBean() {
20 FilterRegistrationBean initXssFilterBean = new FilterRegistrationBean();
21 initXssFilterBean.setFilter(new XSSEscapeFilter());
22 initXssFilterBean.setOrder(1);
23 initXssFilterBean.setEnabled(true);
24 initXssFilterBean.addUrlPatterns("/*");
25 initXssFilterBean.setDispatcherTypes(DispatcherType.REQUEST);
26 return initXssFilterBean;
27 }
28}
29
到这里基本上就拦截到参数了,你可以自己定义对参数的修改规则。也可以使用jsoup对XSS进行过滤
脚本过滤
使用 jsoup 对参数中的 标签进行过滤
添加依赖
1<dependency>
2 <groupId>org.jsoup</groupId>
3 <artifactId>jsoup</artifactId>
4 <version>1.11.3</version>
5</dependency>
完整的 XssHttpServletRequestWrapper 代码:
1import org.apache.commons.lang3.StringUtils;
2import org.jsoup.Jsoup;
3import org.jsoup.nodes.Document;
4import org.jsoup.safety.Whitelist;
5
6import javax.servlet.ReadListener;
7import javax.servlet.ServletInputStream;
8import javax.servlet.http.HttpServletRequest;
9import javax.servlet.http.HttpServletRequestWrapper;
10import java.io.*;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.Map;
14
15/**
16 * 实现XSS过滤
17 * Create by zdRan on 2018/5/8
18 *
19 * @author cm.zdran@gmail.com
20 */
21public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
22 private HttpServletRequest orgRequest = null;
23 /**
24 * 配置可以通过过滤的白名单
25 * /
26 private static final Whitelist whitelist = new Whitelist();
27 /**
28 * 配置过滤化参数,不对代码进行格式化
29 */
30 private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
31
32 public XssHttpServletRequestWrapper(HttpServletRequest request) {
33 super(request);
34 orgRequest = request;
35
36 }
37
38 @Override
39 public ServletInputStream getInputStream() throws IOException {
40
41 BufferedReader br = new BufferedReader(new InputStreamReader(orgRequest.getInputStream()));
42 String line = br.readLine();
43 String result = "";
44 if (line != null) {
45 result += clean(line);
46 }
47
48 return new WrappedServletInputStream(new ByteArrayInputStream(result.getBytes()));
49 }
50
51 /**
52 * 覆盖getParameter方法,将参数名和参数值都做xss过滤。
53 * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
54 * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
55 */
56 @Override
57 public String getParameter(String name) {
58 if (("content".equals(name) || name.endsWith("WithHtml"))) {
59 return super.getParameter(name);
60 }
61 name = clean(name);
62 String value = super.getParameter(name);
63 if (StringUtils.isNotBlank(value)) {
64 value = clean(value);
65 }
66 return value;
67 }
68
69 @Override
70 public Map getParameterMap() {
71 Map map = super.getParameterMap();
72 // 返回值Map
73 Map<String, String> returnMap = new HashMap<String, String>();
74 Iterator entries = map.entrySet().iterator();
75 Map.Entry entry;
76 String name = "";
77 String value = "";
78 while (entries.hasNext()) {
79 entry = (Map.Entry) entries.next();
80 name = (String) entry.getKey();
81 Object valueObj = entry.getValue();
82 if (null == valueObj) {
83 value = "";
84 } else if (valueObj instanceof String[]) {
85 String[] values = (String[]) valueObj;
86 for (int i = 0; i < values.length; i++) {
87 value = values[i] + ",";
88 }
89 value = value.substring(0, value.length() - 1);
90 } else {
91 value = valueObj.92 [native code]
93}">toString();
94 }
95 returnMap.put(name, clean(value).trim());
96 }
97 return returnMap;
98 }
99
100 @Override
101 public String[] getParameterValues(String name) {
102 String[] arr = super.getParameterValues(name);
103 if (arr != null) {
104 for (int i = 0; i < arr.length; i++) {
105 arr[i] = clean(arr[i]);
106 }
107 }
108 return arr;
109 }
110
111
112 /**
113 * 覆盖getHeader方法,将参数名和参数值都做xss过滤。
114 * 如果需要获得原始的值,则通过super.getHeaders(name)来获取
115 * getHeaderNames 也可能需要覆盖
116 */
117 @Override
118 public String getHeader(String name) {
119
120 name = clean(name);
121 String value = super.getHeader(name);
122 if (StringUtils.isNotBlank(value)) {
123 value = clean(value);
124 }
125 return value;
126 }
127
128 /**
129 * 获取最原始的request
130 *
131 * @return
132 */
133 public HttpServletRequest getOrgRequest() {
134 return orgRequest;
135 }
136
137 /**
138 * 获取最原始的request的静态方法
139 *
140 * @return
141 */
142 public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
143 if (req instanceof XssHttpServletRequestWrapper) {
144 return ((XssHttpServletRequestWrapper) req).getOrgRequest();
145 }
146
147 return req;
148 }
149
150 public String clean(String content) {
151 String result = Jsoup.clean(content, "", whitelist, outputSettings);
152 return result;
153 }
154
155 private class WrappedServletInputStream extends ServletInputStream {
156 public void setStream(InputStream stream) {
157 this.stream = stream;
158 }
159
160 private InputStream stream;
161
162 public WrappedServletInputStream(InputStream stream) {
163 this.stream = stream;
164 }
165
166 @Override
167 public int read() throws IOException {
168 return stream.read();
169 }
170
171 @Override
172 public boolean isFinished() {
173 return true;
174 }
175
176 @Override
177 public boolean isReady() {
178 return true;
179 }
180
181 @Override
182 public void setReadListener(ReadListener readListener) {
183
184 }
185 }
186}
好了。到这就算结束了,不过目前还有一个小问题。
使用 jsoup 是可以过滤掉所有的html标签,但是也有个问题,比如
参数是: {“name”:”<html”,”passwd”:”12345”},过滤后的结果是:{“name”:”
因为没有找到标签的结束位置,所以就会过滤掉后面所有的参数。
这样就会导致 controller 获取参数的时候异常。
转载请注明出处
本文链接:zdran.com/20180511.ht…