mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-31 14:48:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			257 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| [[servlet-httpfirewall]]
 | |
| = HttpFirewall
 | |
| Spring Security has several areas where patterns you have defined are tested against incoming requests to decide how the request should be handled.
 | |
| This occurs when the `FilterChainProxy` decides which filter chain a request should be passed through and when the `FilterSecurityInterceptor` decides which security constraints apply to a request.
 | |
| 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 https://www.ietf.org/rfc/rfc2396.txt[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 https://www.owasp.org/index.php/HTTP_Response_Splitting[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:
 | |
| 
 | |
| .Allow Matrix Variables
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| public StrictHttpFirewall httpFirewall() {
 | |
|     StrictHttpFirewall firewall = new StrictHttpFirewall();
 | |
|     firewall.setAllowSemicolon(true);
 | |
|     return firewall;
 | |
| }
 | |
| ----
 | |
| 
 | |
| XML::
 | |
| +
 | |
| [source,xml,role="secondary"]
 | |
| ----
 | |
| <b:bean id="httpFirewall"
 | |
|     class="org.springframework.security.web.firewall.StrictHttpFirewall"
 | |
|     p:allowSemicolon="true"/>
 | |
| 
 | |
| <http-firewall ref="httpFirewall"/>
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| fun httpFirewall(): StrictHttpFirewall {
 | |
|     val firewall = StrictHttpFirewall()
 | |
|     firewall.setAllowSemicolon(true)
 | |
|     return firewall
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| To protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[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:
 | |
| 
 | |
| 
 | |
| .Allow Only GET & POST
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| public StrictHttpFirewall httpFirewall() {
 | |
|     StrictHttpFirewall firewall = new StrictHttpFirewall();
 | |
|     firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
 | |
|     return firewall;
 | |
| }
 | |
| ----
 | |
| 
 | |
| XML::
 | |
| +
 | |
| [source,xml,role="secondary"]
 | |
| ----
 | |
| <b:bean id="httpFirewall"
 | |
|       class="org.springframework.security.web.firewall.StrictHttpFirewall"
 | |
|       p:allowedHttpMethods="GET,POST"/>
 | |
| 
 | |
| <http-firewall ref="httpFirewall"/>
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @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 https://jira.spring.io/browse/SPR-16851[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.
 | |
| 
 | |
| 
 | |
| [[servlet-httpfirewall-headers-parameters]]
 | |
| `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`:
 | |
| 
 | |
| .Allow Any Header Name, Header Value, and Parameter Name
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| public StrictHttpFirewall httpFirewall() {
 | |
|     StrictHttpFirewall firewall = new StrictHttpFirewall();
 | |
|     firewall.setAllowedHeaderNames((header) -> true);
 | |
|     firewall.setAllowedHeaderValues((header) -> true);
 | |
|     firewall.setAllowedParameterNames((parameter) -> true);
 | |
|     return firewall;
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @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:
 | |
| 
 | |
| .Allow Certain User Agents
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @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::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @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:
 | |
| 
 | |
| .Parse Headers As UTF-8
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| firewall.setAllowedHeaderValues((header) -> {
 | |
|     String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
 | |
|     return allowed.matcher(parsed).matches();
 | |
| });
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| firewall.setAllowedHeaderValues {
 | |
|     val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
 | |
|     return allowed.matcher(parsed).matches()
 | |
| }
 | |
| ----
 | |
| ======
 |