Search code examples
javakotlinspring-securityrequest-headers

Spring security StrictHttpFirewall isn't validating the headers


I am trying to add my custom StrictHttpFirewall to be able to reject requests in case of headers are not matching my pattern.

I have the following configuration of Spring security:

@Configuration
@EnableWebSecurity
class SecurityConfig {

    companion object {
        private val HEADER_NAME_PATTERN = "^[a-z1-9_-]{1,512}$".toRegex(RegexOption.IGNORE_CASE)
        private val HEADER_VALUE_BASE_PATTERN = "[\\p{IsAssigned}&&[^\\p{IsControl}]]*".toRegex()
        private val HEADER_VALUE_ERROR_PATTERN = "[<>]".toRegex()
    }

    @Bean
    fun requestRejectedHandler(): RequestRejectedHandler = ErrorResponseRequestRejectedHandler()

    @Bean
    fun httpFirewall(): HttpFirewall {
        val firewall = StrictHttpFirewall()
        val headerNames = { header: String -> HEADER_NAME_PATTERN.matches(header) }
        val headerValues = { value: String ->
            HEADER_VALUE_BASE_PATTERN.containsMatchIn(value).and(
                !HEADER_VALUE_ERROR_PATTERN.containsMatchIn(value)
            )
        }
        firewall.setAllowedHeaderNames(headerNames)
        firewall.setAllowedHeaderValues(headerValues)
        firewall.setAllowedHttpMethods(
            listOf(GET.name(), POST.name(), PUT.name(), PATCH.name(), DELETE.name())
        )
        return firewall
    }

    @Bean
    @Throws(Exception::class)
    fun filterChain(http: HttpSecurity): SecurityFilterChain =
        http
            .authorizeHttpRequests { it.anyRequest().permitAll() }
            .sessionManagement {
                it
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .disable()
            }
            .headers {
                it
                    .frameOptions { option -> option.sameOrigin() }
                    .xssProtection(Customizer.withDefaults())
                    .cacheControl { cache -> cache.disable() }
                    .contentSecurityPolicy(Customizer.withDefaults())
            }
            .cors { it.disable() }
            .csrf { it.disable() }
            .requestCache { it.disable() }
            .build()
}

But unfortunately it's not working, I am still able to pass headers that is not match my regex. I have a long debugging session, and it seams like request itself (made by mockMVC) having all the headers, but spring security is not checking the headers from request itself. Interesting note: it's validating the header that I specified as mandatory on my controller method parameter level - like @RequestHeader(value = "my-header", required = true). Would be appreciate for any help.

Already tried:

  1. Explicitly set httpFirewall to WebSecurity - no success
  2. Added custom filter to debug the headers, as result header names become validated, but just because I explicitly called the request.headerNames.toList() because the request itself is wrapped internally with HttpFirewallRequest implementation, that have overriden method getHeaders with the validation that I actually expected.
  3. Make request not only from mockMvc but also RestTemplate and just make the request via postman - no success

Solution

  • StrictHttpFirewall checks for a header but lazily. Also, you have to notice that StrictHttpFirewall wraps the request and response both into Wrapper.

    With this, StrictHttpFirewall won't actually reject the request unless down the filterChain, a request is made to access one of the black listed headers. It means even if you have a blacklisted header, but nobody in filter chain or controller is trying to read from that header, your request won't be rejected until it is attempted to be read.

    In PR where such protection was introduced, a discussion was held related to performance of such header check as this check would be executed multiple times.

    To address your requirement, I don't think this can be handled by StrictHttpFirewall. To reject a request eagerly, you will have to write a filter that sits at the beginning of filter chain, or at the mvc layer, checks for presence of such headers and rejects it.

    One advantage that you can have by introducing request in filter chain is FilterChainProxy accepts a RequestReject(ed)Handler that will be able to handle RequestReject(ed)Exception should you choose to throw it.