Spring Security StrictHttpFirewall 没有验证头部信息。

huangapple go评论112阅读模式
英文:

Spring security StrictHttpFirewall isn't validating the headers

问题

我正在尝试添加自定义的StrictHttpFirewall,以便在标头不匹配我的模式时能够拒绝请求。

我有以下Spring安全配置:

@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()
}

但不幸的是,它不起作用,我仍然可以传递不匹配我的正则表达式的标头。我进行了长时间的调试会话,似乎请求本身(由mockMVC制作)具有所有标头,但Spring安全未检查来自请求本身的标头。有趣的是:它正在验证我在控制器方法参数级别指定为必需的标头,例如@RequestHeader(value = "my-header", required = true)。非常感谢任何帮助。

已经尝试过:
1)明确将httpFirewall设置为WebSecurity - 无法成功
2)添加自定义过滤器以调试标头,结果标头名称变得有效,但这仅因为我显式调用了request.headerNames.toList()而导致的,因为请求本身在内部使用了HttpFirewallRequest实现,该实现具有我实际预期的验证的重写方法getHeaders。
3)不仅从mockMvc而且还从RestTemplate进行请求,并仅通过postman进行请求 - 无法成功

英文:

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 = &quot;^[a-z1-9_-]{1,512}$&quot;.toRegex(RegexOption.IGNORE_CASE)
        private val HEADER_VALUE_BASE_PATTERN = &quot;[\\p{IsAssigned}&amp;&amp;[^\\p{IsControl}]]*&quot;.toRegex()
        private val HEADER_VALUE_ERROR_PATTERN = &quot;[&lt;&gt;]&quot;.toRegex()
    }

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

    @Bean
    fun httpFirewall(): HttpFirewall {
        val firewall = StrictHttpFirewall()
        val headerNames = { header: String -&gt; HEADER_NAME_PATTERN.matches(header) }
        val headerValues = { value: String -&gt;
            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 -&gt; option.sameOrigin() }
                    .xssProtection(Customizer.withDefaults())
                    .cacheControl { cache -&gt; 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

答案1

得分: 0

StrictHttpFirewall懒惰地检查一个标头。此外,您必须注意 StrictHttpFirewall 将请求和响应都包装在 Wrapper 中。

因此,只有在 filterChain 下发出请求来访问黑名单标头之一时,StrictHttpFirewall 才会实际拒绝请求。这意味着即使您有一个黑名单标头,但在过滤器链或控制器中没有人尝试从该标头读取,除非尝试读取它,否则不会拒绝您的请求。

在引入此类保护的 PR 中,曾就此类标头检查的性能进行了讨论,因为此检查将被多次执行。

为了满足您的要求,我认为 StrictHttpFirewall 无法处理此问题。要急切地拒绝请求,您将需要编写一个位于过滤器链开头或 MVC 层的过滤器,检查是否存在这些标头并拒绝它。

通过在过滤器链中引入请求,您可以获得的一个优势是 FilterChainProxy 接受一个 RequestReject(ed)Handler,该处理程序将能够处理 RequestReject(ed)Exception,如果您选择抛出它。

英文:

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.

huangapple
  • 本文由 发表于 2023年8月10日 15:53:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76873667.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定