HttpFirewall

了解机制和测试定义模式时使用的 URL 值非常重要。

The servlet specification defines several properties for the HttpServletRequest that are accessible via getter methods and that we might want to match against. These are the contextPath, servletPath, pathInfo, and queryString. Spring Security is only interested in securing paths within the application, so the contextPath is ignored. Unfortunately, the servlet spec does not define exactly what the values of servletPath and pathInfo contain for a particular request URI. For example, each path segment of a URL may contain parameters, as defined in RFC 2396 (You have probably seen this when a browser does not support cookies and the jsessionid parameter is appended to the URL after a semicolon. However, the RFC allows the presence of these parameters in any path segment of the URL.) The Specification does not clearly state whether these should be included in the servletPath and pathInfo values and the behavior varies between different servlet containers. There is a danger that, when an application is deployed in a container that does not strip path parameters from these values, an attacker could add them to the requested URL to cause a pattern match to succeed or fail unexpectedly. (The original values will be returned once the request leaves the FilterChainProxy, so will still be available to the application.) Other variations in the incoming URL are also possible. For example, it could contain path-traversal sequences (such as /../) or multiple forward slashes (//) that could also cause pattern-matches to fail. Some containers normalize these out before performing the servlet mapping, but others do not. To protect against issues like these, FilterChainProxy uses an HttpFirewall strategy to check and wrap the request. By default, un-normalized requests are automatically rejected, and path parameters and duplicate slashes are removed for matching purposes. (So, for example, an original request path of /secure;hack=1/somefile.html;hack=2 is returned as /secure/somefile.html.) It is, therefore, essential that a FilterChainProxy is used to manage the security filter chain. Note that the servletPath and pathInfo values are decoded by the container, so your application should not have any valid paths that contain semi-colons, as these parts are removed for matching purposes.

如前所述,默认策略是使用 Ant 风格路径进行匹配,这可能是大多数用户的最佳选择。该策略在 AntPathRequestMatcher 类中实现,该类使用 Spring 的 AntPathMatcher 对模式与连接的 servletPathpathInfo 进行不区分大小写的匹配,忽略 queryString

如果您需要更强大的匹配策略,可以使用正则表达式。然后策略实现是 RegexRequestMatcher。有关更多信息,请参阅 此类的 Javadoc

在实践中,我们建议您在服务层使用方法安全来控制对应用程序的访问,而不是完全依赖于在 Web 应用程序级别定义的安全约束。URL 会发生变化,很难考虑应用程序可能支持的所有可能的 URL 以及请求可能如何被操纵。您应该限制自己使用一些简单的 Ant 路径,这些路径易于理解。始终尝试使用“默认拒绝”方法,其中您最后定义了一个通配符(/)来拒绝访问。

在服务层定义的安全更加健壮,更难绕过,因此您应该始终利用 Spring Security 的方法安全选项。

HttpFirewall 还通过拒绝 HTTP 响应标头中的换行符来防止 HTTP 响应拆分

默认情况下,使用 StrictHttpFirewall 实现。此实现拒绝似乎是恶意的请求。如果它对您的需求过于严格,您可以自定义拒绝的请求类型。但是,重要的是您在知道这可能会使您的应用程序容易受到攻击的情况下这样做。例如,如果您希望使用 Spring MVC 的矩阵变量,您可以使用以下配置

允许矩阵变量
  • Java

  • XML

  • Kotlin

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
<b:bean id="httpFirewall"
    class="org.springframework.security.web.firewall.StrictHttpFirewall"
    p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}

为了防止 跨站点跟踪 (XST)HTTP 动词篡改StrictHttpFirewall 提供了一个允许的有效 HTTP 方法列表。默认的有效方法是 DELETEGETHEADOPTIONSPATCHPOSTPUT。如果您的应用程序需要修改有效方法,您可以配置一个自定义的 StrictHttpFirewall bean。以下示例仅允许 HTTP GETPOST 方法

仅允许 GET 和 POST 方法
  • Java

  • XML

  • Kotlin

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowedHttpMethods="GET,POST"/>

<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}

如果您使用 `new MockHttpServletRequest()`,它目前会创建一个 HTTP 方法,其值为一个空字符串(`""`)。这是一种无效的 HTTP 方法,会被 Spring Security 拒绝。您可以通过将其替换为 `new MockHttpServletRequest("GET", "")` 来解决此问题。有关请求改进此问题的 issue,请参见 SPR_16851

如果您必须允许任何 HTTP 方法(不推荐),可以使用 `StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`。这样做会完全禁用 HTTP 方法的验证。

`StrictHttpFirewall` 还会检查头部名称和值以及参数名称。它要求每个字符都具有定义的代码点,并且不是控制字符。

可以通过使用以下方法来放宽或调整此要求:

  • StrictHttpFirewall#setAllowedHeaderNames(Predicate)

  • StrictHttpFirewall#setAllowedHeaderValues(Predicate)

  • StrictHttpFirewall#setAllowedParameterNames(Predicate)

参数值也可以通过 `setAllowedParameterValues(Predicate)` 进行控制。

例如,要关闭此检查,您可以将 `StrictHttpFirewall` 与始终返回 `true` 的 `Predicate` 实例连接起来。

允许任何头部名称、头部值和参数名称
  • Java

  • Kotlin

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHeaderNames { true }
    firewall.setAllowedHeaderValues { true }
    firewall.setAllowedParameterNames { true }
    return firewall
}

或者,您可能需要允许某个特定值。

例如,iPhone Xʀ 使用的 `User-Agent` 包含一个不在 ISO-8859-1 字符集中的字符。由于这个原因,一些应用程序服务器会将此值解析为两个单独的字符,第二个字符是一个未定义的字符。

您可以使用 `setAllowedHeaderValues` 方法解决此问题。

允许某些用户代理
  • Java

  • Kotlin

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
    Pattern userAgent = ...;
    firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
    return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
    val userAgent = Pattern.compile(...)
    firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
    return firewall
}

对于头部值,您也可以考虑在验证时将其解析为 UTF-8。

将头部解析为 UTF-8
  • Java

  • Kotlin

firewall.setAllowedHeaderValues((header) -> {
    String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
    return allowed.matcher(parsed).matches();
});
firewall.setAllowedHeaderValues {
    val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
    return allowed.matcher(parsed).matches()
}