一、先来个引子Bug引出今天的主角
1、如何发现这个问题的:
由于客户的安全扫描,发现我们的某个旧版服务中使用着已被发现CVE漏洞的Spring Security,要求我们必须修复,那我们肯定满足其要求,将SpringSecurity升级到更新的满足要求的版本;我们选择了:spring-security-web(5.5.7)
业主很开心,之前的安全漏洞修复了,但是很快就发现了问题,一些之前没问题的接口,接二连三地被发现了问题,比如URL存在汉字或一些特殊字符的时候,请求就不会成功;
通过日志,我们可以发现报了类似以下错误:
org.springframework.security.web.firewall.RequestRejectedException: The servletPath was rejected because it can only contain printable ASCII characters. 或者: org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";" 等等
2、用一个简单的例子,复现一下这个问题:
pom.xml依赖:
<dependencies> <dependency> <groupId>com.jiguiquan.starter</groupId> <artifactId>web-starter</artifactId> <version>2.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 工作中不用引入,为了复现问题故意引入的 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.5.7</version> </dependency> </dependencies>
TestFireWallController.java:
@RestController @RequestMapping("/test/") public class TestFireWallController { @GetMapping("/demo/{word}") public String test(@PathVariable("word") String word){ String response = "传入进来的word为:" + word; return response; } }
运行代码后,我们发起一个简单的请求,肯定是没有问题的:
但是当我们请求中存在汉字时(忽略我的异常捕获的提示语):
我们去看一下日志:
看名字我们就能知道,这是严格版的防火墙:StrictHttpFirewall(新版Spring Security默认使用的防火墙策略)
3、先快速解决这个问题:
我们常见的方案就是使用更低约束的防火墙策略,Spring Security也为我们提供了这样的一个实现:DefaultHttpFirewall
我们在 SecurityConfig.java 中增加以下的配置即可:
@Bean public HttpFirewall defaultHttpFirewall() { return new DefaultHttpFirewall(); } @Override public void configure(WebSecurity web) throws Exception { web.httpFirewall(defaultHttpFirewall()); }
4、此时,我们再来进行刚刚的测试看看:
解决问题就是如此简单!
二、更深入一点的了解下Spring Security的FireWall防火墙
1、SpringSecurity中提供了哪些防火墙策略?
可以看到,HttpFirewall默认共有2个实现:
-
DefaultHttpFirewall:旧版本默认的防火墙策略,在较高的版本中已经不再是默认项 ——(约束方法很少);
-
StrictHttpFirewall:较高的Spring Security中默认的防火墙策略,较严格 ——(约束方法很多);
三、StrictHttpFirewall到底为我们做了哪些防护措施
1、setAllowedHttpMethods(Collection):只允许白名单中的方法 —— 允许设置
通过createDefaultAllowedHttpMethods()方法,我们可以知道缺省的允许的HTTP方法有以下7种:
private static Set<String> createDefaultAllowedHttpMethods() { Set<String> result = new HashSet(); result.add(HttpMethod.DELETE.name()); result.add(HttpMethod.GET.name()); result.add(HttpMethod.HEAD.name()); result.add(HttpMethod.OPTIONS.name()); result.add(HttpMethod.PATCH.name()); result.add(HttpMethod.POST.name()); result.add(HttpMethod.PUT.name()); return result; }
2、isNormalized():请求必须是标准化的URL —— 不可关闭
# 在StrictHttpFirewall中该规则不能被禁用,因为关掉该限制风险极高。 ## 在其requestURI/contextPath/servletPath/pathInfo中,必须不能包含以下字符串序列: ### ["//", "./", "/…/", "/."] private static boolean isNormalized(HttpServletRequest request) { if (!isNormalized(request.getRequestURI())) { return false; } else if (!isNormalized(request.getContextPath())) { return false; } else if (!isNormalized(request.getServletPath())) { return false; } else { return isNormalized(request.getPathInfo()); } } — requestURI : URL中去除协议,主机名,端口之后其余的部分, — contextPath : requestURI中对应webapp的部分, — servletPath : requestURI中对应识别Servlet的部分 — pathInfo : requestURI中去掉contextPath,servletPath剩下的部分
3、setAllowedHttpMethods(boolean):请求地址种不能带有分号 —— 允许关闭
4、containsOnlyPrintableAsciiCharacters():必须是可打印的ASCII字符 —— 不可关闭
5、setAllowUrlEncodedDoubleSlash(boolean):双斜杠不被允许 —— 允许关闭
6、setAllowBackSlash(boolean):反斜杠不被允许 —— 允许关闭
7、setAllowUrlEncodedPercent(boolean):%百分号不被允许 —— 允许关闭
8、setAllowUrlEncodedPeriod(boolean):英文句号(.)不被允许 —— 允许关闭