diff --git a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java index b980f635a7..9a6ceeca9b 100644 --- a/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java @@ -101,6 +101,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { private static final String ATT_POLICY_DIRECTIVES = "policy-directives"; + private static final String ATT_HEADER_VALUE = "header-value"; + private static final String CACHE_CONTROL_ELEMENT = "cache-control"; private static final String HPKP_ELEMENT = "hpkp"; @@ -595,6 +597,14 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser { } builder.addPropertyValue("block", block); } + XXssProtectionHeaderWriter.HeaderValue headerValue = XXssProtectionHeaderWriter.HeaderValue + .from(xssElt.getAttribute(ATT_HEADER_VALUE)); + if (headerValue != null) { + if (disabled) { + attrNotAllowed(parserContext, ATT_HEADER_VALUE, ATT_DISABLED, xssElt); + } + builder.addPropertyValue("headerValue", headerValue); + } if (disabled) { return; } diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc index c6e6d93f0b..5ee3a4b885 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc @@ -1299,6 +1299,9 @@ xss-protection.attlist &= xss-protection.attlist &= ## Add mode=block to the header or not, default is on. attribute block {xsd:boolean}? +xss-protection.attlist &= + ## Specify the value for the X-Xss-Protection header. When set, overrides both enabled and block attributes. + attribute header-value {"0"|"1"|"1; mode=block"}? content-type-options = ## Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd index ed2636a8ea..9a61dd6d41 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd @@ -3652,6 +3652,20 @@ + + + Specify the value for the X-Xss-Protection header. When set, overrides both enabled and + block attributes. + + + + + + + + + + diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java index 8001cc8448..eac36db7ff 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java @@ -384,6 +384,58 @@ public class HttpHeadersConfigTests { // @formatter:on } + @Test + public void requestWhenSettingXssProtectionHeaderValueToZeroThenDefaultsToZero() throws Exception { + Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); + excludedHeaders.remove("X-XSS-Protection"); + this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueZero")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-XSS-Protection", "0")) + .andExpect(excludes(excludedHeaders)); + // @formatter:on + } + + @Test + public void requestWhenSettingXssProtectionHeaderValueToOneThenDefaultsToOne() throws Exception { + Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); + excludedHeaders.remove("X-XSS-Protection"); + this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOne")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-XSS-Protection", "1")) + .andExpect(excludes(excludedHeaders)); + // @formatter:on + } + + @Test + public void requestWhenSettingXssProtectionHeaderValueToOneModeBlockThenDefaultsToOneModeBlock() throws Exception { + Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); + excludedHeaders.remove("X-XSS-Protection"); + this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-XSS-Protection", "1; mode=block")) + .andExpect(excludes(excludedHeaders)); + // @formatter:on + } + + @Test + public void requestWhenSettingXssProtectionDisabledHeaderValueToOneThenDefaultsToOne() throws Exception { + Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); + excludedHeaders.remove("X-XSS-Protection"); + this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionDisabledAndHeaderValueOne")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-XSS-Protection", "1")) + .andExpect(excludes(excludedHeaders)); + // @formatter:on + } + @Test public void configureWhenXssProtectionDisabledAndBlockSetThenAutowireFails() { /* @@ -656,6 +708,13 @@ public class HttpHeadersConfigTests { .withMessageContaining("block"); } + @Test + public void configureWhenXssProtectionDisabledAndHeaderValueSpecifiedThenAutowireFails() { + assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( + () -> this.spring.configLocations(this.xml("XssProtectionDisabledSpecifyingHeaderValue")).autowire()) + .withMessageContaining("header-value"); + } + @Test public void configureWhenFrameOptionsDisabledAndPolicySpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionDisabledAndHeaderValueOne.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionDisabledAndHeaderValueOne.xml new file mode 100644 index 0000000000..37df83724e --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionDisabledAndHeaderValueOne.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOne.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOne.xml new file mode 100644 index 0000000000..e574306c49 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOne.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock.xml new file mode 100644 index 0000000000..a9df4a4539 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueZero.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueZero.xml new file mode 100644 index 0000000000..eb83165e66 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueZero.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-XssProtectionDisabledSpecifyingHeaderValue.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-XssProtectionDisabledSpecifyingHeaderValue.xml new file mode 100644 index 0000000000..9b2bc54c3b --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-XssProtectionDisabledSpecifyingHeaderValue.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index aca8ca3e51..b218588066 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -577,6 +577,12 @@ This indicates to the browser that the page should not be loaded at all. When false and xss-protection-enabled is true, the page will still be rendered when an reflected attack is detected but the response will be modified to protect against the attack. Note that there are sometimes ways of bypassing this mode which can often times make blocking the page more desirable. +[[nsa-xss-protection-header-value]] +* **xss-protection-header-value** +Explicitly set the value for https://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent[reflected / Type-1 Cross-Site Scripting (XSS)] header. +One of: "0", "1", "1; mode=block". +When set, overrides both enabled and block attributes. + [[nsa-xss-protection-parents]] === Parent Elements of diff --git a/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java index 58d54de941..4c6b3e101f 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java @@ -149,6 +149,15 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter { this.value = value; } + public static HeaderValue from(String headerValue) { + for (HeaderValue value : values()) { + if (value.toString().equals(headerValue)) { + return value; + } + } + return null; + } + @Override public String toString() { return this.value;