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:
Daniel Garnier-Moiroux
2022-09-27 14:56:00 +02:00
committed by Steve Riesenberg
parent 7b1158ddb7
commit 93250013e4
13 changed files with 439 additions and 56 deletions

View File

@@ -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 {

View File

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

View File

@@ -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";