Skip to content

Commit a5b23e6

Browse files
committed
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 spring-projectsgh-9631
1 parent 506e50b commit a5b23e6

File tree

13 files changed

+438
-55
lines changed

13 files changed

+438
-55
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

+40
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,10 @@ private XXssConfig() {
729729
* If false, will not specify the mode as blocked. In this instance, any content
730730
* will be attempted to be fixed. If true, the content will be replaced with "#".
731731
* @param enabled the new value
732+
* @deprecated use
733+
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
732734
*/
735+
@Deprecated
733736
public XXssConfig block(boolean enabled) {
734737
this.writer.setBlock(enabled);
735738
return this;
@@ -757,12 +760,49 @@ public XXssConfig block(boolean enabled) {
757760
* X-XSS-Protection: 0
758761
* </pre>
759762
* @param enabled the new value
763+
* @deprecated use
764+
* {@link XXssConfig#headerValue(XXssProtectionHeaderWriter.HeaderValue)} instead
760765
*/
766+
@Deprecated
761767
public XXssConfig xssProtectionEnabled(boolean enabled) {
762768
this.writer.setEnabled(enabled);
763769
return this;
764770
}
765771

772+
/**
773+
* Sets the value of the X-XSS-PROTECTION header. OWASP recommends using
774+
* {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}.
775+
*
776+
* If {@link XXssProtectionHeaderWriter.HeaderValue#DISABLED}, will specify that
777+
* X-XSS-Protection is disabled. For example:
778+
*
779+
* <pre>
780+
* X-XSS-Protection: 0
781+
* </pre>
782+
*
783+
* If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED}, will contain a value
784+
* of 1, but will not specify the mode as blocked. In this instance, any content
785+
* will be attempted to be fixed. For example:
786+
*
787+
* <pre>
788+
* X-XSS-Protection: 1
789+
* </pre>
790+
*
791+
* If {@link XXssProtectionHeaderWriter.HeaderValue#ENABLED_MODE_BLOCK}, will
792+
* contain a value of 1 and will specify mode as blocked. The content will be
793+
* replaced with "#". For example:
794+
*
795+
* <pre>
796+
* X-XSS-Protection: 1 ; mode=block
797+
* </pre>
798+
* @param headerValue the new header value
799+
* @since 5.8
800+
*/
801+
public XXssConfig headerValue(XXssProtectionHeaderWriter.HeaderValue headerValue) {
802+
this.writer.setHeaderValue(headerValue);
803+
return this;
804+
}
805+
766806
/**
767807
* Disables X-XSS-Protection header (does not include it)
768808
* @return the {@link HeadersConfigurer} for additional configuration

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+12
Original file line numberDiff line numberDiff line change
@@ -2859,6 +2859,18 @@ public HeaderSpec disable() {
28592859
return HeaderSpec.this;
28602860
}
28612861

2862+
/**
2863+
* Sets the value of x-xss-protection header. OWASP recommends using
2864+
* {@link XXssProtectionServerHttpHeadersWriter.HeaderValue#DISABLED}.
2865+
* @param headerValue the headerValue
2866+
* @return the {@link HeaderSpec} to continue configuring
2867+
* @since 5.8
2868+
*/
2869+
public HeaderSpec headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue headerValue) {
2870+
HeaderSpec.this.xss.setHeaderValue(headerValue);
2871+
return HeaderSpec.this;
2872+
}
2873+
28622874
}
28632875

28642876
/**

config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,16 +16,21 @@
1616

1717
package org.springframework.security.config.web.server
1818

19+
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter.HeaderValue
20+
1921
/**
2022
* A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
2123
* idiomatic Kotlin code.
2224
*
25+
* @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
26+
*
2327
* @author Eleftheria Stein
2428
* @since 5.4
2529
*/
2630
@ServerSecurityMarker
2731
class ServerXssProtectionDsl {
2832
private var disabled = false
33+
var headerValue: HeaderValue? = null
2934

3035
/**
3136
* Disables cache control response headers
@@ -36,6 +41,7 @@ class ServerXssProtectionDsl {
3641

3742
internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
3843
return { xss ->
44+
headerValue?.also { xss.headerValue(headerValue) }
3945
if (disabled) {
4046
xss.disable()
4147
}

config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.config.web.servlet.headers
1818

1919
import org.springframework.security.config.annotation.web.builders.HttpSecurity
2020
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
21+
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue
2122

2223
/**
2324
* A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
@@ -28,11 +29,15 @@ import org.springframework.security.config.annotation.web.configurers.HeadersCon
2829
* @property block whether to specify the mode as blocked
2930
* @property xssProtectionEnabled if true, the header value will contain a value of 1.
3031
* If false, will explicitly disable specify that X-XSS-Protection is disabled.
32+
* @property headerValue the value of the X-XSS-Protection header. OWASP recommends [HeaderValue.DISABLED].
3133
*/
3234
@HeadersSecurityMarker
3335
class XssProtectionConfigDsl {
36+
@Deprecated("use headerValue instead")
3437
var block: Boolean? = null
38+
@Deprecated("use headerValue instead")
3539
var xssProtectionEnabled: Boolean? = null
40+
var headerValue: HeaderValue? = null
3641

3742
private var disabled = false
3843

@@ -47,6 +52,7 @@ class XssProtectionConfigDsl {
4752
return { xssProtection ->
4853
block?.also { xssProtection.block(block!!) }
4954
xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
55+
headerValue?.also { xssProtection.headerValue(headerValue) }
5056

5157
if (disabled) {
5258
xssProtection.disable()

config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java

+56-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
3737
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
3838
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
3939
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
40+
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
4041
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
4142
import org.springframework.test.web.servlet.MockMvc;
4243
import org.springframework.test.web.servlet.MvcResult;
@@ -58,6 +59,7 @@
5859
* @author Vedran Pavic
5960
* @author Eleftheria Stein
6061
* @author Marcus Da Coregio
62+
* @author Daniel Garnier-Moiroux
6163
*/
6264
@ExtendWith(SpringTestContextExtension.class)
6365
public class HeadersConfigurerTests {
@@ -171,6 +173,15 @@ public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssPr
171173
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
172174
}
173175

176+
@Test
177+
public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledThenOnlyXssProtectionHeaderInResponse()
178+
throws Exception {
179+
this.spring.register(XssProtectionValueDisabledConfig.class).autowire();
180+
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
181+
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
182+
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
183+
}
184+
174185
@Test
175186
public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception {
176187
this.spring.register(XssProtectionInLambdaConfig.class).autowire();
@@ -179,6 +190,15 @@ public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeade
179190
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
180191
}
181192

193+
@Test
194+
public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueDisabledInLambdaThenOnlyXssProtectionHeaderInResponse()
195+
throws Exception {
196+
this.spring.register(XssProtectionValueDisabledInLambdaConfig.class).autowire();
197+
MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
198+
.andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")).andReturn();
199+
assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION);
200+
}
201+
182202
@Test
183203
public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception {
184204
this.spring.register(HeadersCustomSameOriginConfig.class).autowire();
@@ -679,6 +699,22 @@ protected void configure(HttpSecurity http) throws Exception {
679699

680700
}
681701

702+
@EnableWebSecurity
703+
static class XssProtectionValueDisabledConfig extends WebSecurityConfigurerAdapter {
704+
705+
@Override
706+
protected void configure(HttpSecurity http) throws Exception {
707+
// @formatter:off
708+
http
709+
.headers()
710+
.defaultsDisabled()
711+
.xssProtection()
712+
.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED);
713+
// @formatter:on
714+
}
715+
716+
}
717+
682718
@EnableWebSecurity
683719
static class XssProtectionInLambdaConfig extends WebSecurityConfigurerAdapter {
684720

@@ -696,6 +732,25 @@ protected void configure(HttpSecurity http) throws Exception {
696732

697733
}
698734

735+
@EnableWebSecurity
736+
static class XssProtectionValueDisabledInLambdaConfig extends WebSecurityConfigurerAdapter {
737+
738+
@Override
739+
protected void configure(HttpSecurity http) throws Exception {
740+
// @formatter:off
741+
http
742+
.headers((headers) ->
743+
headers
744+
.defaultsDisabled()
745+
.xssProtection((xXssConfig) ->
746+
xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED)
747+
)
748+
);
749+
// @formatter:on
750+
}
751+
752+
}
753+
699754
@EnableWebSecurity
700755
static class HeadersCustomSameOriginConfig extends WebSecurityConfigurerAdapter {
701756

config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.springframework.security.config.test.SpringTestContext;
3232
import org.springframework.security.config.test.SpringTestContextExtension;
3333
import org.springframework.security.web.header.writers.StaticHeadersWriter;
34+
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
3435
import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy;
3536
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
3637
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
@@ -273,8 +274,7 @@ protected void configure(HttpSecurity http) throws Exception {
273274
// xss-protection@enabled and xss-protection@block
274275
.defaultsDisabled()
275276
.xssProtection()
276-
.xssProtectionEnabled(true)
277-
.block(false);
277+
.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
278278
// @formatter:on
279279
}
280280

config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -296,6 +296,51 @@ public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten()
296296
assertHeaders();
297297
}
298298

299+
@Test
300+
public void headersWhenXssProtectionValueDisabledThenXssProtectionWritten() {
301+
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
302+
// @formatter:off
303+
this.http.headers()
304+
.xssProtection()
305+
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED);
306+
// @formatter:on
307+
assertHeaders();
308+
}
309+
310+
@Test
311+
public void headersWhenXssProtectionValueEnabledThenXssProtectionWritten() {
312+
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1");
313+
// @formatter:off
314+
this.http.headers()
315+
.xssProtection()
316+
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED);
317+
// @formatter:on
318+
assertHeaders();
319+
}
320+
321+
@Test
322+
public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() {
323+
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
324+
// @formatter:off
325+
this.http.headers()
326+
.xssProtection()
327+
.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK);
328+
// @formatter:on
329+
assertHeaders();
330+
}
331+
332+
@Test
333+
public void headersWhenXssProtectionValueDisabledInLambdaThenXssProtectionWritten() {
334+
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0");
335+
// @formatter:off
336+
this.http.headers()
337+
.xssProtection((xssProtection) ->
338+
xssProtection.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED)
339+
);
340+
// @formatter:on
341+
assertHeaders();
342+
}
343+
299344
@Test
300345
public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() {
301346
String policyDirectives = "Feature-Policy";

config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -96,4 +96,29 @@ class ServerXssProtectionDslTests {
9696
}
9797
}
9898
}
99+
100+
@Test
101+
fun `request when xss protection value disabled then xss header in response`() {
102+
this.spring.register(XssValueDisabledConfig::class.java).autowire()
103+
104+
this.client.get()
105+
.uri("/")
106+
.exchange()
107+
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0")
108+
}
109+
110+
@EnableWebFluxSecurity
111+
@EnableWebFlux
112+
open class XssValueDisabledConfig {
113+
@Bean
114+
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
115+
return http {
116+
headers {
117+
xssProtection {
118+
headerValue = XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED
119+
}
120+
}
121+
}
122+
}
123+
}
99124
}

0 commit comments

Comments
 (0)