当前位置: 首页 > 科技观察

SpringSecurity系列的请求防火墙是默认开启的

时间:2023-03-21 16:48:42 科技观察

之前有朋友说,看SpringSecurity这么麻烦,不如自己写个Filter拦截请求,简单实用。当然自己写也可以实现,但是大多数情况下大家都不是专业的web安全工程师,所以要考虑的问题只有认证和授权。处理完这两个问题后,系统似乎就很安全了。其实不然!各种各样的web攻击每天都在发生,固定会话攻击,csrf攻击等等,如果你不了解这些攻击,那么你做出来的系统肯定无法防御这些攻击。使用SpringSecurity的好处是,即使你不了解这些攻击,也不必担心这些攻击,因为SpringSecurity已经为你做好了防御。我们常说SpringSecurity比Shiro更重量级,它有重量级的好处,比如功能全,安全管理更完善。使用SpringSecurity,您甚至不知道您的系统有多安全!今天就和大家聊一聊SpringSecurity自带的防火墙机制。好了,废话不多说,我们来看文章。1、HttpFirewall在SpringSecurity中提供了一个HttpFirewall。看名字就知道这是一个请求防火墙,可以自动处理一些非法请求。HttpFirewall目前有两个实现类:一个是严格模式防火墙设置,一个是默认防火墙设置。DefaultHttpFirewall的限制比StrictHttpFirewall宽松,这当然意味着安全性不如StrictHttpFirewall。StrictHttpFirewall在SpringSecurity中默认使用。2.保护措施那么StrictHttpFirewall是如何保护我们的应用程序的呢?让我们一一看看。2.1只允许白名单中的方法首先,对于请求的方法,只允许白名单中的方法,即不是所有的HTTP请求方法都可以执行。从StrictHttpFirewall的源码可以看出:添加(HttpMethod.GET.name());result.add(HttpMethod.HEAD.name());result.add(HttpMethod.OPTIONS.name());result.add(HttpMethod.PATCH.name());结果.add(HttpMethod.POST.name());result.add(HttpMethod.PUT.name());returnresult;}privatevoidrejectForbiddenHttpMethod(HttpServletRequestrequest){if(this.allowedHttpMethods==ALLOW_ANY_HTTP_METHOD){return;}if(!this.allowedHttpMethods.contains(request.getMethod())){thrownewRequestRejectedException("TherequestwasrejectedbecausetheHTTPmethod\""+request.getMethod()+"\"wasnotincludedwithinthewhitelist"+tthis.al}pthloweds};}从这段代码我们可以看出,你的HTTP请求方式一定是DELETE、GET、HEAD、OPTIONS、PATCH、POST、PUT其中之一,请求才能发送成功,否则会抛出RequestRejectedException。如果想发送其他的HTTP请求方式,比如TRACE,应该怎么办?我们只需要自己重新提供一个StrictHttpFirewall实例,如下:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setUnsafeAllowAnyHttpMethod(true);returnfirewall;}其中setUnsafeAllowAnyHttpMethod方法是指任何HTTP请求方法都可以得到验证。或者您也可以重新定义可以通过setAllowedHttpMethods方法传递的方法。2.2请求地址不能有分号。不知道你有没有试过。如果使用SpringSecurity,请求地址不能有;。如果请求地址有;,会自动跳转到如下页面:可以看到页面提示已经说了,因为你的请求地址包含;,所以请求失败。什么时候会;包含在请求地址中?不知道大家有没有注意到,在使用Shiro的时候,如果禁用cookie,地址栏会出现jsessionid,像这样:http://localhost:8080/hello;jsessionid=xx这种传递jsessionid的方法是其实很不安全(松哥会在下一篇文章和大家讨论这个问题),所以在SpringSecurity中,这种传递参数的方式默认为Disabled。当然,如果你想让地址栏出现;那么可以这样设置:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setAllowSemicolon(true);returnfirewall;}设置完成后,去访问同一个界面,可以看到虽然报错了这时候还是报错,错误是404,不是一开始的不允许的;这是错误的。注意在URL地址中,%3b或%3B之后;编码,因此%3b或%3B不能出现在地址中。有的小伙伴可能不知道或者没用过。从Spring3.2开始,带来了一种全新的传参方式@MatrixVariable。@MatrixVariable是Spring3.2自带的函数。该方法扩展了请求参数的传输格式,参数之间可以用;隔开。因为SpringSecurity默认禁止这种传递参数的方式,所以一般情况下,如果需要使用@MatrixVariable来标记参数,就得在SpringSecurity中额外释放。接下来,我将通过一个简单的例子向大家展示@MatrixVariable的用法。我们创建一个新的/hello方法:@RequestMapping(value="/hello/{id}")publicvoidhello(@PathVariableIntegerid,@MatrixVariableStringname){System.out.println("id="+id);System.out.println("name="+name);}另外,我们还需要配置SpringMVC,这样;不会被自动移除:@ConfigurationpublicclassWebMvcConfigextendsWebMvcConfigurationSupport{@OverrideprotectedvoidconfigurePathMatch(PathMatchConfigurerconfigurer){UrlPathHelperurlPathHelper=newUrlPathHelper();urlPathHelperem.lonerConerseRemt)setUrlPathHelper(urlPathHelper);}}然后允许启动URL也已配置到SpringSecurity注意项目(;),浏览器发送如下请求:http://localhost:8080/hello/123;name=javaboyconsole打印信息如下:id=123name=javaboy可以看到@MatrixVariable注解生效了。2.3必须是规范的URL请求地址必须是规范的URL。什么是标准化网址?标准化的URL主要从四个方面来判断。我们看源码:StrictHttpFirewall#isNormalized:privatestaticbooleanisNormalized(HttpServletRequestrequest){if(!isNormalized(request.getRequestURI())){returnfalse;}if(!isNormalized(request.getContextPath()){returnfalse;}if(!isNormalized(request.getServletPath())){returnfalse;}if(!isNormalized(request.getPathInfo())){returnfalse;}returntrue;}getRequestURI是获取请求协议以外的字符;getContextPath是获取上下文path,相当于项目名;getServletPath是请求的servlet路径,getPathInfo是去掉contextPath和servletPath后剩下的部分。这四个路径都不能包含如下字符串:"./","/../"or"/."2.4必须是可打印的ASCII字符如果请求地址包含不可打印的ASCII字符,请求将被拒绝,我们从源码中可以看出端倪:StrictHttpFirewall#containsOnlyPrintableAsciiCharactersprivatestaticbooleancontainsOnlyPrintableAsciiCharacters(Stringuri){intlength=uri.length();for(inti=0;i'\u007e'){returnfalse;}}returntrue;}2.5不允许使用双斜线如果请求地址中有双斜线,请求也会被拒绝。双斜杠//URL地址编码后为%2F%2F,其中F的大小写无所谓,所以"%2f%2f"、"%2f%2F"、"%2F%2f可能没有出现在请求地址中”,“%2F%2F”。如果希望在请求地址中出现//,可以这样配置:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setAllowUrlEncodedDoubleSlash(true);returnfirewall;}如果%出现在2.6%是不允许的请求地址,这个请求也会被拒绝。URL编码的%是%25,所以%25也不能出现在URL地址中。如果希望请求地址中出现%,可以修改如下:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setAllowUrlEncodedPercent(true);returnfirewall;}2.7请求地址不允许正反斜杠包含斜杠%2F或斜杠编码后的%2f,请求将被拒绝。如果请求URL包含反斜杠\或反斜杠编码字符%5C或%5c,请求将被拒绝。如果想去掉上面两个限制,可以这样配置:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setAllowBackSlash(true);firewall.setAllowUrlEncodedSlash(true);returnfirewall;}2.8.不允许如果请求If字符%2e和%2E之后。地址中存在编码,请求将被拒绝。如果需要支持,配置如下:@BeanHttpFirewallhttpFirewall(){StrictHttpFirewallfirewall=newStrictHttpFirewall();firewall.setAllowUrlEncodedPeriod(true);returnfirewall;}2.9总结需要强调的是,上面所说的限制都是针对requestURI的request进行了限制,不对请求参数进行限制。比如你的请求格式是:http://localhost:8080/hello?param=aa%2ebb,那么2.7节中提到的限制就与你无关了。这个大家从StrictHttpFirewall源码中很容易看到:publicclassStrictHttpFirewallimplementsHttpFirewall{@OverridepublicFirewalledRequestgetFirewalledRequest(HttpServletRequestrequest)throwsRequestRejectedException{rejectForbiddenHttpMethod(request);rejectedBlacklistedUrls(request);rejectedUntrustedHosts(request);if(!isNormalized(request)){thrownewRequestRejectedException("TherequestwasrejectedbecausetheURLwasnotnormalized.");}StringrequestUri=request.getRequestURI();if(!containsOnlyPrintableAsciiCharacters(requestUri)){thrownewRequestRejectedException("TherequestURIwasrejectedbecauseitcanonlycontainprintableASCIIcharacters.");}returnnewFirewalledRequest(request){@Overridepublicvoidreset(){}};}privatevoidrejectedBlacklistedUrls)(HStringforbidden:this.encodedUrlBlacklist){if(encodedUrlContains(request,forbidden)){thrownewRequestRejectedException("TherequestwasrejectedbecausetheURLcontainedapotentiallymaliciousString\""+forbidden+"\"");}}for(Stringforbidden:this.decodedUrlBlacklist){if(decodedUrlContains(request,forbidden)){thrownewRequestRejectedException("TherequestwasrejectedbecausetheURLcontainedapotentiallymaliciousString\""+forbidden+"\"");}}}privatestaticbooleanencodedUrlContains(HttpServletRequestrequest,Stringvalue){if(valueContains(request.getContextPath(),value)){returntrue;}returnvalueContains(request.getRequestURI(),value);}privatestaticbooleandecodedUrlContains(HquetpServletContextPath(),value)){getServletPath(),value)){returntrue;}if(valueContains(request.getPathInfo(),value)){returntrue;}returnfalse;}privatestaticbooleanvalueContains(Stringvalue,Stringcontains){returnvalue!=null&&value.contains(包含);}}rejectedBlacklistedUrls方法是对URL进行验证,这个方法的逻辑很简单,就不细说了。注意:虽然我们可以在SpringSecurity中手动修改这些限制,但松哥不建议大家进行任何更改。每次休息iction有它的每一次限制放开,都会带来未知的安全隐患。稍后宋哥在给大家分享Web中的安全攻击时,会再次提到这些限制的作用,请大家关注。3.总结没想到吧?SpringSecurity为你做了这么多事!刚回复鸡汤:你所谓的平静岁月,不过是有人替你背负。