SpringMVC中的安全问题-如何防止CSRF-XSS

摘要

CSRF(Cross-site request forgery跨站请求伪造)和XSS(Cross site script跨站脚本攻击)

CSRF会盗取浏览器cookie,获得web服务器的身份认证等,从而达到访问跨站服务器资源,也被称为:one click attack/session riding,缩写为:CSRF/XSRF

XSS会注入脚本,从而执行脚本后获取用户信息,盗取身份等。

本文重点介绍XSS的防御,CSRF介绍,不写例子了。

如何防御XSS

从上面知道,XSS通过请求注入执行脚本,那么从那里注入的?用户提交的表单数据,请求带的参数,请求头等都有可能,这些参数无非是从Request对象获取,我们只要在服务器获取这些数据之前,对数据进行过滤,保证没有XSS风险就可以了。

还有一种特殊的,就是Restful path中的参数,比如Spring MVC中/xxx/{a}/{b}/resouce,PathVariable,a和b怎么防止xss,因为它并不是通过Request对象传递过来的。

过滤Request中的参数

通常的做法是写一个XSSFilter,里面用HttpServletRequestWrapper,包装Request对象,在HttpServletRequestWrapper子类中,对xss过滤。

代码

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
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
/**
* @author Liu Hailin
* @create 2017-07-21 下午3:45
**/
@Slf4j
@WebFilter(filterName = "xssFilter", urlPatterns = "/*", asyncSupported = true)
public class XssFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info( "[XssFilter]...init..." );
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
XssHttpServletRequestWapper xssRequest =
new XssHttpServletRequestWapper( (HttpServletRequest)servletRequest );
filterChain.doFilter( xssRequest, servletResponse );
}

@Override
public void destroy() {
log.info( "[XssFilter]...destory..." );
}

}

class XssHttpServletRequestWapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWapper(HttpServletRequest request) {
super( request );
}

@Override
public String getParameter(String name) {
return HtmlUtils.htmlEscape( super.getParameter( name ) );
}

@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues( name );

if (values != null) {
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
escapseValues[i] = HtmlUtils.htmlEscape( values[i] );
}
return escapseValues;
}
}
return super.getParameterValues( name );
}

@Override
public String getHeader(String name) {
String header = super.getHeader( name );
if (null != header) {
return HtmlUtils.htmlEscape( header );
}
return null;
}

@Override
public Enumeration<String> getHeaders(String name) {

Enumeration<String> enumeration = super.getHeaders( name );
Vector<String> re = new Vector<String>();
while (enumeration.hasMoreElements()) {
String e = enumeration.nextElement();
String ae = HtmlUtils.htmlEscape( e );
re.add( ae );
}
return re.elements();
}

注意到一个类XssHttpServletRequestWapper继承HttpServletRequestWrapper,覆写了getParameter,getParameterValues,getHeader,getHeaders等方法。并用spring 提供的工具HtmlUtils过滤xss。

怎么处理PathVariable中xss

用SpingMVC时觉得应该有一个解析UrlPath的类来完成这个工作(就像HandlerMapping、ViewResovler一样),让用户自定义,但实际上并没有。分析源码url匹配解析是在AbstractHandlerMapping这个类中。

1
2
3
4
5
6
7
/* org.springframework.web.servlet.handler.AbstractHandlerMapping 类 */
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Object defaultHandler;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
/* 省略代码... */

其中UrlPathHelper 类中包含decodePathVariables和decodeMatrixVariables两个方法即为抽取path参数,所以自定义一个UrlPathHelper来完成参数过滤功能。然后所有AbstractHandlerMapping均使用自定义UrlPathHelper即可。但是需要注意的是SpringMVC默认产生了两个HandlerMapping->RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,其是在WebMvcConfigurationSupport这个类中产生的,所有必须设置这两个HandlerMapping的urlPathHelper,没有直接的调用接口,那么监听Spring的bean实例化即可,创建类如下:

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
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
public class XssHandlerMappingPostProcessor implements BeanPostProcessor{

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{
if(bean instanceof AbstractHandlerMapping){
AbstractHandlerMapping ahm = (AbstractHandlerMapping) bean;
ahm.setUrlPathHelper(new XssUrlPathHelper());
}

return bean;
}

static class XssUrlPathHelper extends UrlPathHelper{

@Override
public Map<String, String> decodePathVariables(HttpServletRequest request, Map<String, String> vars){
Map<String, String> result = super.decodePathVariables(request, vars);
if(!CollectionUtils.isEmpty(result)){
for(String key : result.keySet()){
result.put(key, cleanXSS(result.get(key)));
}
}

return result;
}

@Override
public MultiValueMap<String, String> decodeMatrixVariables(HttpServletRequest request,
MultiValueMap<String, String> vars){
MultiValueMap<String, String> mvm = super.decodeMatrixVariables(request, vars);
if(!CollectionUtils.isEmpty(mvm)){
for(String key : mvm.keySet()){
List<String> value = mvm.get(key);
for(int i = 0; i < value.size(); i++){
value.set(i, cleanXSS(value.get(i)));
}
}
}

return mvm;
}

private String cleanXSS(String value){
return HtmlUtils.htmlEscape(value);
}

}

}

如何防御CSRF

在业界目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证

#####验证 HTTP Referer 字段

根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址,Referer 的值是由浏览器提供的,每个浏览器的实现方式有区别,本身也可能有安全漏洞,也会被伪造。所以不能把referer作为唯一判断依据,可以作为手段之一使用。

新版本浏览器有设置,用户可以设置浏览器发请求,不发送Referer,这样就没有办法采用这种方式。但是在系统内部Rest调用我们可以自己指定Referer去验证。

在请求地址中添加 token 并验证

比如JWT,客户端获取服务器生成的Token,每次带着token访问,token有效期很短,会过期,过期后,客户端需要再次获取。适用于Web应用。

在 HTTP 头中自定义属性并验证

与第二种方法类似,只不过是在请求头中添加token,服务端验证请求头是否有token。适用于Rest API的请求。

CSRF 是一种危害非常大的攻击,又很难以防范。目前几种防御策略虽然可以很大程度上抵御 CSRF 的攻击,但并没有一种完美的解决方案。

坚持技术分享,您的支持将鼓励我继续创作!