From 8ca88d1bfb3bf7973d89eafa5ea3da2624c0434c Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Mon, 3 Oct 2022 15:45:17 +0200 Subject: [PATCH] Add X-Xss-Protection headerValue to XML config Issue gh-9631 --- .../http/HeadersBeanDefinitionParser.java | 10 ++++ .../security/config/spring-security-5.8.rnc | 3 + .../security/config/spring-security-5.8.xsd | 14 +++++ .../config/http/HttpHeadersConfigTests.java | 59 +++++++++++++++++++ ...XssProtectionDisabledAndHeaderValueOne.xml | 36 +++++++++++ ...isabledWithXssProtectionHeaderValueOne.xml | 36 +++++++++++ ...thXssProtectionHeaderValueOneModeBlock.xml | 36 +++++++++++ ...sabledWithXssProtectionHeaderValueZero.xml | 36 +++++++++++ ...rotectionDisabledSpecifyingHeaderValue.xml | 36 +++++++++++ .../servlet/appendix/namespace/http.adoc | 6 ++ .../writers/XXssProtectionHeaderWriter.java | 9 +++ 11 files changed, 281 insertions(+) create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionDisabledAndHeaderValueOne.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOne.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueZero.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-XssProtectionDisabledSpecifyingHeaderValue.xml 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 b980f635a73..9a6ceeca9ba 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 @@ private void parseXssElement(boolean addIfNotPresent, Element element, ParserCon } 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 5e61b3ee746..1a2ac4c21a7 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 @@ -1298,6 +1298,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 b5642bb2931..b1e90f7b408 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 @@ -3649,6 +3649,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 088bd5334d8..4008a07e03e 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 void requestWhenDisablingXssProtectionThenDefaultsToZero() throws Excepti // @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() { assertThatExceptionOfType(BeanCreationException.class) @@ -650,6 +702,13 @@ public void configureWhenXssProtectionDisabledAndBlockSpecifiedThenAutowireFails .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 00000000000..37df83724e2 --- /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 00000000000..e574306c49a --- /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 00000000000..a9df4a45398 --- /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 00000000000..eb83165e66a --- /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 00000000000..9b2bc54c3b3 --- /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 5491bd45fdb..f660c8b9e1b 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -578,6 +578,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 59111d40d4e..4f16ac3bfd6 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 enum HeaderValue { 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;