此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3! |
Http防火墙
了解机制是什么以及根据您定义的模式进行测试时使用的 URL 值非常重要。
servlet 规范为HttpServletRequest
可通过 getter 方法访问,并且我们可能想要匹配它们。这些是contextPath
,servletPath
,pathInfo
和queryString
. Spring Security 只对保护应用程序中的路径感兴趣,因此contextPath
被忽略。不幸的是,servlet 规范并没有准确定义servletPath
和pathInfo
include 的特定请求 URI。例如,URL 的每个路径段可能包含 RFC 2396 中定义的参数(当浏览器不支持 cookie 和jsessionid
参数附加到 URL 的分号后。但是,RFC 允许在 URL 的任何路径段中存在这些参数。规范没有明确说明这些参数是否应包含在servletPath
和pathInfo
值,并且行为在不同的 servlet 容器之间有所不同。存在一种危险,即当应用程序部署在未从这些值中剥离路径参数的容器中时,攻击者可能会将它们添加到请求的 URL 中,以导致模式匹配意外成功或失败。(一旦请求离开FilterChainProxy
,因此应用程序仍可使用。传入 URL 中的其他变体也是可能的。例如,它可以包含路径遍历序列(例如/../
) 或多个正斜杠 () 也可能导致模式匹配失败。一些容器在执行 servlet 映射之前对这些进行规范化,但其他容器则不会。为了防止此类问题,//
FilterChainProxy
使用HttpFirewall
策略来检查和包装请求。默认情况下,未规范化的请求会自动被拒绝,并且路径参数和重复的斜杠将被删除以进行匹配。(因此,例如,原始请求路径/secure;hack=1/somefile.html;hack=2
返回为/secure/somefile.html
.) 因此,必须FilterChainProxy
用于管理安全过滤器链。请注意,servletPath
和pathInfo
值由容器解码,因此应用程序不应具有任何包含分号的有效路径,因为出于匹配目的,这些部分将被删除。
前面说过,默认策略是使用 Ant 样式的路径进行匹配,这很可能是大多数用户的最佳选择。该策略在类中实现AntPathRequestMatcher
,它使用 Spring 的AntPathMatcher
对模式与串联的servletPath
和pathInfo
,忽略queryString
.
如果您需要更强大的匹配策略,可以使用正则表达式。然后策略实现是RegexRequestMatcher
. 请参阅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 方法的允许列表。
默认的有效方法是DELETE
,GET
,HEAD
,OPTIONS
,PATCH
,POST
和PUT
.
如果您的应用程序需要修改有效方法,您可以配置自定义StrictHttpFirewall
豆。
以下示例仅允许 HTTPGET
和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
}
如果您使用 |
如果必须允许任何 HTTP 方法(不推荐),则可以使用StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)
.
这样做会完全禁用 HTTP 方法的验证。
StrictHttpFirewall
还检查标头名称和值以及参数名称。
它要求每个字符都有一个定义的代码点,而不是控制字符。
可以使用以下方法根据需要放宽或调整此要求:
-
StrictHttpFirewall#setAllowedHeaderNames(Predicate)
-
StrictHttpFirewall#setAllowedHeaderValues(Predicate)
-
StrictHttpFirewall#setAllowedParameterNames(Predicate)
参数值也可以通过 |
例如,要关闭此检查,您可以将StrictHttpFirewall
跟Predicate
始终返回的实例true
:
-
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:
-
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()
}