对于最新的稳定版本,请使用 Spring Security 6.4.1! |
HttpFirewall 防火墙
了解机制是什么以及在针对您定义的模式进行测试时使用的 URL 值非常重要。
Servlet 规范为HttpServletRequest
可以通过 getter 方法访问,并且我们可能想要匹配它们。
这些是contextPath
,servletPath
,pathInfo
和queryString
.
Spring Security 只对保护应用程序中的路径感兴趣,因此contextPath
被忽略。
不幸的是,servlet 规范并没有准确定义servletPath
和pathInfo
contain 的请求 URI。
例如,URL 的每个路径段可能包含 RFC 2396 中定义的参数(当浏览器不支持 Cookie 并且jsessionid
parameter 附加到 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 风格的路径进行匹配,这很可能是大多数用户的最佳选择。
该策略在 class 中实现AntPathRequestMatcher
,它使用 Spring 的AntPathMatcher
执行模式与串联的servletPath
和pathInfo
,忽略queryString
.
如果您需要更强大的匹配策略,可以使用正则表达式。
然后,策略实施是RegexRequestMatcher
.
有关更多信息,请参阅此类的 Javadoc。
在实践中,我们建议您在服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于在 Web 应用程序级别定义的安全约束的使用。
URL 会发生变化,因此很难考虑应用程序可能支持的所有可能的 URL 以及可能如何处理请求。
您应该限制自己使用一些易于理解的简单 Ant 路径。
始终尝试使用“默认拒绝”方法,其中您有一个全能通配符 (/
或) 最后定义以拒绝访问。
在服务层定义的安全性更加健壮且更难绕过,因此您应该始终利用 Spring Security 的方法安全选项。
这HttpFirewall
还可以通过拒绝 HTTP 响应标头中的换行符来防止 HTTP 响应拆分。
默认情况下,StrictHttpFirewall
implementation 的 implementation 的 API 中。
此实现拒绝看似恶意的请求。
如果它对您的需求来说太严格,您可以自定义拒绝的请求类型。
但是,请务必知道这可能会使您的应用程序受到攻击。
例如,如果你希望使用 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,HEAD"/>
<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
}
对于 Headers 值,您可以考虑在验证时将它们解析为 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()
}