This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.1.12! |
HttpFirewall
It’s important to understand what the mechanism is and what URL value is used when testing against the patterns that you define.
The Servlet Specification defines several properties for the HttpServletRequest
which are accessible via getter methods, and which 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
will contain for a particular request URI.
For example, each path segment of a URL may contain parameters, as defined in RFC 2396
[1].
The Specification does not clearly state whether these should be included in the servletPath
and pathInfo
values and the behaviour varies between different servlet containers.
There is a danger that when an application is deployed in a container which does not strip path parameters from these values, an attacker could add them to the requested URL in order to cause a pattern match to succeed or fail unexpectedly.
[2].
Other variations in the incoming URL are also possible.
For example, it could contain path-traversal sequences (like /../
) or multiple forward slashes (//
) which could also cause pattern-matches to fail.
Some containers normalize these out before performing the servlet mapping, but others don’t.
To protect against issues like these, FilterChainProxy
uses an HttpFirewall
strategy to check and wrap the request.
Un-normalized requests are automatically rejected by default, and path parameters and duplicate slashes are removed for matching purposes.
[3].
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 which contain semi-colons, as these parts will be removed for matching purposes.
As mentioned above, 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
.
If for some reason, you need a more powerful matching strategy, you can use regular expressions.
The strategy implementation is then RegexRequestMatcher
.
See the Javadoc for this class for more information.
In practice we recommend that you use method security at your service layer, to control access to your application, and do not rely entirely on the use of security constraints defined at the web-application level. URLs change and it is difficult to take account of all the possible URLs that an application might support and how requests might be manipulated. You should try and restrict yourself to using a few simple ant paths which are simple to understand. Always try to use a "deny-by-default" approach where you have a catch-all wildcard ( / or ) defined last and denying access.
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.
The HttpFirewall
also prevents HTTP Response Splitting by rejecting new line characters in the HTTP Response headers.
By default the StrictHttpFirewall
is used.
This implementation rejects requests that appear to be malicious.
If it is too strict for your needs, then 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 leverage Spring MVC’s Matrix Variables, the following configuration could be used:
-
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
}
The StrictHttpFirewall
provides an allowed list of valid HTTP methods that are allowed to protect against Cross Site Tracing (XST) and HTTP Verb Tampering.
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.
For example, the following will only allow 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,HEAD"/>
<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
val firewall = StrictHttpFirewall()
firewall.setAllowedHttpMethods(listOf("GET", "POST"))
return firewall
}
If you are using |
If you must allow any HTTP method (not recommended), you can use StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)
.
This will disable validation of the HTTP method entirely.
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 using the following methods:
-
StrictHttpFirewall#setAllowedHeaderNames(Predicate)
-
StrictHttpFirewall#setAllowedHeaderValues(Predicate)
-
StrictHttpFirewall#setAllowedParameterNames(Predicate)
Also, parameter values can be controlled with setAllowedParameterValues(Predicate) .
|
For example, to switch off this check, you can wire your StrictHttpFirewall
with Predicate
s that always return true
, like so:
-
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
}
Or, there might be a specific value that you need to allow.
For example, iPhone Xʀ uses a User-Agent
that includes a character not in the ISO-8859-1 charset.
Due to this fact, some application servers will parse this value into two separate characters, the latter being an undefined character.
You can address this with the setAllowedHeaderValues
method, as you can see below:
-
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
}
In the case of header values, you may instead consider parsing them as UTF-8 at verification time like so:
-
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()
}
jsessionid
parameter is appended to the URL after a semi-colon. However the RFC allows the presence of these parameters in any path segment of the URL
FilterChainProxy
, so will still be available to the application.
/secure;hack=1/somefile.html;hack=2
will be returned as /secure/somefile.html
.