Make X-Xss-Protection configurable through ServerHttpSecurity
OWASP recommends using "X-Xss-Protection: 0". The default is currently "X-Xss-Protection: 1; mode=block". In 6.0, the default will be "0". This commits adds the ability to configure the xssProtection header value in ServerHttpSecurity. This commit deprecates the use of "enabled" and "block" booleans to configure XSS protection, as the state "!enabled + block" is invalid. This impacts HttpSecurity. Issue gh-9631
This commit is contained in:
committed by
Steve Riesenberg
parent
7b1158ddb7
commit
93250013e4
+40
@@ -729,7 +729,10 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
* If false, will not specify the mode as blocked. In this instance, any content
|
||||
* will be attempted to be fixed. If true, the content will be replaced with "#".
|
||||
* @param enabled the new value
|
||||
* @deprecated use
|
||||
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public XXssConfig block(boolean enabled) {
|
||||
this.writer.setBlock(enabled);
|
||||
return this;
|
||||
@@ -757,12 +760,49 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
* X-XSS-Protection: 0
|
||||
* </pre>
|
||||
* @param enabled the new value
|
||||
* @deprecated use
|
||||
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public XXssConfig xssProtectionEnabled(boolean enabled) {
|
||||
this.writer.setEnabled(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the X-XSS-PROTECTION header. OWASP recommends using
|
||||
* {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}.
|
||||
*
|
||||
* If {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}, will specify that
|
||||
* X-XSS-Protection is disabled. For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 0
|
||||
* </pre>
|
||||
*
|
||||
* If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED}, will contain a value
|
||||
* of 1, but will not specify the mode as blocked. In this instance, any content
|
||||
* will be attempted to be fixed. For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1
|
||||
* </pre>
|
||||
*
|
||||
* If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED_MODE_BLOCK}, will
|
||||
* contain a value of 1 and will specify mode as blocked. The content will be
|
||||
* replaced with "#". For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1 ; mode=block
|
||||
* </pre>
|
||||
* @param headerValue the new header value
|
||||
* @since 5.8
|
||||
*/
|
||||
public XXssConfig headerValue(XXssProtectionHeaderWriter.HeaderValue headerValue) {
|
||||
this.writer.setHeaderValue(headerValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables X-XSS-Protection header (does not include it)
|
||||
* @return the {@link HeadersConfigurer} for additional configuration
|
||||
|
||||
+12
@@ -2859,6 +2859,18 @@ public class ServerHttpSecurity {
|
||||
return HeaderSpec.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of x-xss-protection header. OWASP recommends using
|
||||
* {@link XXssProtectionServerHttpHeadersWriter.HeaderValue#DISABLED}.
|
||||
* @param headerValue the headerValue
|
||||
* @return the {@link HeaderSpec} to continue configuring
|
||||
* @since 5.8
|
||||
*/
|
||||
public HeaderSpec headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue headerValue) {
|
||||
HeaderSpec.this.xss.setHeaderValue(headerValue);
|
||||
return HeaderSpec.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,16 +16,21 @@
|
||||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter.HeaderValue
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
@ServerSecurityMarker
|
||||
class ServerXssProtectionDsl {
|
||||
private var disabled = false
|
||||
var headerValue: HeaderValue? = null
|
||||
|
||||
/**
|
||||
* Disables cache control response headers
|
||||
@@ -36,6 +41,7 @@ class ServerXssProtectionDsl {
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
|
||||
return { xss ->
|
||||
headerValue?.also { xss.headerValue(headerValue) }
|
||||
if (disabled) {
|
||||
xss.disable()
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.config.web.servlet.headers
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
|
||||
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
|
||||
@@ -28,11 +29,15 @@ import org.springframework.security.config.annotation.web.configurers.HeadersCon
|
||||
* @property block whether to specify the mode as blocked
|
||||
* @property xssProtectionEnabled if true, the header value will contain a value of 1.
|
||||
* If false, will explicitly disable specify that X-XSS-Protection is disabled.
|
||||
* @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
|
||||
*/
|
||||
@HeadersSecurityMarker
|
||||
class XssProtectionConfigDsl {
|
||||
@Deprecated("use headerValue instead")
|
||||
var block: Boolean? = null
|
||||
@Deprecated("use headerValue instead")
|
||||
var xssProtectionEnabled: Boolean? = null
|
||||
var headerValue: HeaderValue? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
@@ -47,6 +52,7 @@ class XssProtectionConfigDsl {
|
||||
return { xssProtection ->
|
||||
block?.also { xssProtection.block(block!!) }
|
||||
xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
|
||||
headerValue?.also { xssProtection.headerValue(headerValue) }
|
||||
|
||||
if (disabled) {
|
||||
xssProtection.disable()
|
||||
|
||||
+56
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,6 +37,7 @@ import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicy
|
||||
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
||||
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -58,6 +59,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* @author Vedran Pavic
|
||||
* @author Eleftheria Stein
|
||||
* @author Marcus Da Coregio
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class HeadersConfigurerTests {
|
||||
@@ -171,6 +173,15 @@ public class HeadersConfigurerTests {
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledThenOnlyXssProtectionHeaderInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(XssProtectionValueDisabledConfig.class).autowire();
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception {
|
||||
this.spring.register(XssProtectionInLambdaConfig.class).autowire();
|
||||
@@ -179,6 +190,15 @@ public class HeadersConfigurerTests {
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledInLambdaThenOnlyXssProtectionHeaderInResponse()
|
||||
throws Exception {
|
||||
this.spring.register(XssProtectionValueDisabledInLambdaConfig.class).autowire();
|
||||
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
|
||||
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
|
||||
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
|
||||
this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
|
||||
@@ -679,6 +699,22 @@ public class HeadersConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class XssProtectionValueDisabledConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers()
|
||||
.defaultsDisabled()
|
||||
.xssProtection()
|
||||
.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@@ -696,6 +732,25 @@ public class HeadersConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class XssProtectionValueDisabledInLambdaConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.headers((headers) ->
|
||||
headers
|
||||
.defaultsDisabled()
|
||||
.xssProtection((xXssConfig) ->
|
||||
xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.header.writers.StaticHeadersWriter;
|
||||
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
|
||||
import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
@@ -273,8 +274,7 @@ public class NamespaceHttpHeadersTests {
|
||||
// xss-protection@enabled and xss-protection@block
|
||||
.defaultsDisabled()
|
||||
.xssProtection()
|
||||
.xssProtectionEnabled(true)
|
||||
.block(false);
|
||||
.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
+46
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -296,6 +296,51 @@ public class HeaderSpecTests {
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionValueDisabledThenXssProtectionWritten() {
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
|
||||
// @formatter:off
|
||||
this.http.headers()
|
||||
.xssProtection()
|
||||
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED);
|
||||
// @formatter:on
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionValueEnabledThenXssProtectionWritten() {
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1");
|
||||
// @formatter:off
|
||||
this.http.headers()
|
||||
.xssProtection()
|
||||
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED);
|
||||
// @formatter:on
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() {
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
|
||||
// @formatter:off
|
||||
this.http.headers()
|
||||
.xssProtection()
|
||||
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK);
|
||||
// @formatter:on
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionValueDisabledInLambdaThenXssProtectionWritten() {
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
|
||||
// @formatter:off
|
||||
this.http.headers()
|
||||
.xssProtection((xssProtection) ->
|
||||
xssProtection.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED)
|
||||
);
|
||||
// @formatter:on
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() {
|
||||
String policyDirectives = "Feature-Policy";
|
||||
|
||||
+26
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -96,4 +96,29 @@ class ServerXssProtectionDslTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when xss protection value disabled then xss header in response`() {
|
||||
this.spring.register(XssValueDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class XssValueDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
xssProtection {
|
||||
headerValue = XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,12 +19,16 @@ package org.springframework.security.config.web.servlet.headers
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.config.web.servlet.invoke
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter
|
||||
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
@@ -138,4 +142,32 @@ class XssProtectionConfigDslTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `headers when XSS protection header value disabled then X-XSS-Protection header is 0`() {
|
||||
this.spring.register(XssProtectionHeaderValueDisabledFunctionConfig::class.java).autowire()
|
||||
|
||||
this.mockMvc.get("/") {
|
||||
secure = true
|
||||
}.andExpect {
|
||||
header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0") }
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class XssProtectionHeaderValueDisabledFunctionConfig () {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
headers {
|
||||
defaultsDisabled = true
|
||||
xssProtection {
|
||||
headerValue = XXssProtectionHeaderWriter.HeaderValue.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user