新版Spring Security自带防火墙,你都不知道自己的服务有多安全

一、先来个引子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;
    }
}

运行代码后,我们发起一个简单的请求,肯定是没有问题的:

1691741715984021.png

但是当我们请求中存在汉字时(忽略我的异常捕获的提示语):

1691741755912491.png

我们去看一下日志:

1691741885333506.png

看名字我们就能知道,这是严格版的防火墙: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、此时,我们再来进行刚刚的测试看看:

1691742998198841.png

解决问题就是如此简单!


二、更深入一点的了解下Spring Security的FireWall防火墙

1、SpringSecurity中提供了哪些防火墙策略?

1692002378500575.png

可以看到,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):英文句号(.)不被允许 —— 允许关闭

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐