HttpFirewall
了解该机制是什么以及测试针对你定义的模式时所使用的 URL 值非常重要。
It is important to understand what the mechanism is and what URL value is used when testing against the patterns that you define.
Servlet 规范为`HttpServletRequest`定义了多个可通过 getter 方法访问的属性,我们可能希望与之匹配。这些是`contextPath`、servletPath
、pathInfo`和`queryString
。Spring Security 仅对保护应用中的路径感兴趣,因此`contextPath`将被忽略。遗憾的是,servlet 规范没有确切定义`servletPath`和`pathInfo`在特定请求 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`值由容器解码,因此你的应用不应包含包含分号的任何有效路径,因为这些部分已被删除以进行匹配。
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
对 concatenated servletPath
和 pathInfo
执行不区分大小写的模式匹配,并忽略 queryString
。
As mentioned earlier, the default strategy is to use Ant-style paths for matching, and this is likely to be the best choice for most users.
The strategy is implemented in the class AntPathRequestMatcher
, which uses Spring’s AntPathMatcher
to perform a case-insensitive match of the pattern against the concatenated servletPath
and pathInfo
, ignoring the queryString
.
如果你需要更强大的匹配策略,可以使用正则表达式。策略实现为`RegexRequestMatcher`。有关更多信息,请参阅该类的{security-api-url}/org/springframework/security/web/util/matcher/RegexRequestMatcher.html[Javadoc]。
If you need a more powerful matching strategy, you can use regular expressions.
The strategy implementation is then RegexRequestMatcher
.
See the {security-api-url}/org/springframework/security/web/util/matcher/RegexRequestMatcher.html[Javadoc for this class] for more information.
实际上,我们建议你在你的服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于在 Web 应用程序层面定义的安全约束。URL 会发生变化,很难考虑应用程序可能支持的所有可能的 URL 以及请求可能如何被操纵。你应该只使用简单易懂的几个 Ant 路径。始终尝试使用"`deny-by-default`"方法,其中你在最后定义一个通配符(/
or )来拒绝访问。
In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level.
URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated.
You should restrict yourself to using a few simple Ant paths that are simple to understand.
Always try to use a “deny-by-default” approach, where you have a catch-all wildcard (/
or ) defined last to deny access.
在服务层定义的安全功能更为强大,也更难绕过,因此你应该始终利用 Spring Security 的方法安全选项。
Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security’s method security options.
`HttpFirewall`还通过拒绝 HTTP 响应头中的换行符来防止 HTTP Response Splitting。
The HttpFirewall
also prevents HTTP Response Splitting by rejecting new line characters in the HTTP Response headers.
默认情况下,使用 StrictHttpFirewall
实现。此实现拒绝看似恶意的请求。如果它对你来说过于严格,你可以自定义拒绝哪种类型的请求。但是,这样做时请务必知道,这可能会让你的应用程序面临攻击的风险。例如,如果你希望使用 Spring MVC 的矩阵变量,则可以使用以下配置:
By default, the StrictHttpFirewall
implementation is used.
This implementation rejects requests that appear to be malicious.
If it is too strict for your needs, you can customize what types of requests are rejected.
However, it is important that you do so knowing that this can open your application up to attacks.
For example, if you wish to use Spring MVC’s matrix variables, you could use the following configuration:
-
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
}
为了防止 Cross Site Tracing (XST)和 HTTP Verb Tampering,StrictHttpFirewall`提供了一个有效的 HTTP 方法允许列表。默认的有效方法是`DELETE
、GET
、HEAD
、OPTIONS
、PATCH
、POST`和`PUT
。如果你的应用需要修改有效方法,你可以配置一个自定义`StrictHttpFirewall`bean。以下示例仅允许 HTTP `GET`和`POST`方法:
To protect against Cross Site Tracing (XST) and HTTP Verb Tampering, the StrictHttpFirewall
provides an allowed list of valid HTTP methods that are allowed.
The default valid methods are DELETE
, GET
, HEAD
, OPTIONS
, PATCH
, POST
, and PUT
.
If your application needs to modify the valid methods, you can configure a custom StrictHttpFirewall
bean.
The following example allows only HTTP GET
and POST
methods:
-
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() If you use |
如果你必须允许任何 HTTP 方法(不推荐),可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)
。这样做会完全禁用 HTTP 方法验证。
If you must allow any HTTP method (not recommended), you can use StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)
.
Doing so entirely disables validation of the HTTP method.
StrictHttpFirewall
还检查标题名称和值以及参数名称。它要求每个字符都有定义的代码点,并且不是控制字符。
StrictHttpFirewall
also checks header names and values and parameter names.
It requires that each character have a defined code point and not be a control character.
可以通过使用以下方法根据需要放宽或调整此要求:
This requirement can be relaxed or adjusted as necessary by using the following methods:
-
StrictHttpFirewall#setAllowedHeaderNames(Predicate)
-
StrictHttpFirewall#setAllowedHeaderValues(Predicate)
-
StrictHttpFirewall#setAllowedParameterNames(Predicate)
还可以通过 Parameter values can be also controlled with |
例如,要关闭此检查,你可以使用始终返回 true
的 Predicate
实例关联你的 StrictHttpFirewall
:
For example, to switch off this check, you can wire your StrictHttpFirewall
with Predicate
instances that always return 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
}
或者,可能存在需要允许的特定值。
Alternatively, there might be a specific value that you need to allow.
例如,iPhone Xʀ 使用包含 ISO-8859-1 字符集内没有的字符的 User-Agent
。由于此原因,某些应用程序服务器将此值解析为两个单独的字符,后者是未定义的字符。
For example, iPhone Xʀ uses a User-Agent
that includes a character that is not in the ISO-8859-1 charset.
Due to this fact, some application servers parse this value into two separate characters, the latter being an undefined character.
你可以通过 setAllowedHeaderValues
方法解决此问题:
You can address this with the setAllowedHeaderValues
method:
-
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:
In the case of header values, you may instead consider parsing them as UTF-8 at verification time:
-
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()
}