跳到主要内容

HttpFirewall

QWen Max 中英对照 HttpFirewall

了解在针对你定义的模式进行测试时所使用的机制和 URL 值是很重要的。

servlet 规范为 HttpServletRequest 定义了几个可通过 getter 方法访问的属性,我们可能需要与这些属性进行匹配。这些属性包括 contextPathservletPathpathInfoqueryString。Spring Security 仅对保护应用程序内的路径感兴趣,因此 contextPath 被忽略。不幸的是,servlet 规范并未明确指定对于特定请求 URI,servletPathpathInfo 应包含的确切值。例如,URL 的每个路径段可能包含参数,如 RFC 2396 中所定义(当浏览器不支持 cookie 时,你可能见过这种情况,jsessionid 参数被附加到 URL 中的分号之后。然而,RFC 允许 URL 的任何路径段中存在这些参数。)规范未明确说明这些参数是否应包含在 servletPathpathInfo 值中,并且不同 servlet 容器的行为各不相同。存在一种风险,即当应用程序部署在不会从这些值中剥离路径参数的容器中时,攻击者可以将它们添加到请求的 URL 中,导致模式匹配意外成功或失败。(原始值将在请求离开 FilterChainProxy 后返回,因此仍可供应用程序使用。)传入 URL 还可能存在其他变化。例如,它可能包含路径遍历序列(如 /../)或多条正斜杠(//),这也会导致模式匹配失败。某些容器在执行 servlet 映射之前会对其进行规范化,但其他容器则不会。为了防止这些问题,FilterChainProxy 使用 HttpFirewall 策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,并且为了匹配目的,路径参数和重复的斜杠会被移除。(因此,例如,原始请求路径 /secure;hack=1/somefile.html;hack=2 会被返回为 /secure/somefile.html。)因此,使用 FilterChainProxy 来管理安全过滤器链是至关重要的。请注意,servletPathpathInfo 值由容器解码,因此你的应用程序不应有任何包含分号的有效路径,因为这些部分在匹配时会被移除。

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

如果你需要一个更强大的匹配策略,你可以使用正则表达式。这时策略实现就是 RegexRequestMatcher。更多信息请参见 RegexRequestMatcher 的 Javadoc。

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

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

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

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

@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
java

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

@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
java
提示

如果你使用 new MockHttpServletRequest(),它当前会创建一个空字符串 ("") 作为 HTTP 方法。这是一个无效的 HTTP 方法,并且会被 Spring Security 拒绝。你可以通过将其替换为 new MockHttpServletRequest("GET", "") 来解决这个问题。有关请求改进此问题的详情,请参阅 SPR_16851

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

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

此要求可以根据需要通过以下方法进行放宽或调整:

  • StrictHttpFirewall#setAllowedHeaderNames(Predicate)

  • StrictHttpFirewall#setAllowedHeaderValues(Predicate)

  • StrictHttpFirewall#setAllowedParameterNames(Predicate)

备注

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

例如,要关闭此检查,可以使用始终返回 truePredicate 实例来连接你的 StrictHttpFirewall

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

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

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

你可以使用 setAllowedHeaderValues 方法来解决这个问题:

@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;
}
java

在处理头部值时,你可以考虑在验证时将其解析为 UTF-8:

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