Skip to content

Latest commit

 

History

History
256 lines (221 loc) · 10.4 KB

File metadata and controls

256 lines (221 loc) · 10.4 KB

HttpFirewall

It is 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 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.

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.

If you need a more powerful matching strategy, you can use regular expressions. The strategy implementation is then RegexRequestMatcher. See the javadoc:org.springframework.security.web.util.matcher.RegexRequestMatcher[] Javadoc for more information.

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.

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 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:

Example 1. Allow Matrix Variables
Java
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
XML
<b:bean id="httpFirewall"
    class="org.springframework.security.web.firewall.StrictHttpFirewall"
    p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>
Kotlin
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}

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:

Example 2. Allow Only GET & POST
Java
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
XML
<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowedHttpMethods="GET,POST"/>

<http-firewall ref="httpFirewall"/>
Kotlin
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}
Tip

If you use new MockHttpServletRequest(), it currently creates an HTTP method as an empty String (""). This is an invalid HTTP method and is rejected by Spring Security. You can resolve this by replacing it with new MockHttpServletRequest("GET", ""). See SPR_16851 for an issue that requests improving this.

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 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)

Note

Parameter values can be also controlled with setAllowedParameterValues(Predicate).

For example, to switch off this check, you can wire your StrictHttpFirewall with Predicate instances that always return true:

Example 3. Allow Any Header Name, Header Value, and Parameter Name
Java
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
Kotlin
@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.

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.

You can address this with the setAllowedHeaderValues method:

Example 4. Allow Certain User Agents
Java
@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;
}
Kotlin
@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:

Example 5. Parse Headers As UTF-8
Java
firewall.setAllowedHeaderValues((header) -> {
    String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
    return allowed.matcher(parsed).matches();
});
Kotlin
firewall.setAllowedHeaderValues {
    val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
    return allowed.matcher(parsed).matches()
}