Skip to content

Commit

Permalink
Make X-Xss-Protection configurable through ServerHttpSecurity
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Kehrlann committed Sep 30, 2022
1 parent 506e50b commit a5b23e6
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,10 @@ private XXssConfig() {
* 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;
Expand Down Expand Up @@ -757,12 +760,49 @@ public XXssConfig block(boolean enabled) {
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2859,6 +2859,18 @@ public HeaderSpec disable() {
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;
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -36,6 +41,7 @@ class ServerXssProtectionDsl {

internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
return { xss ->
headerValue?.also { xss.headerValue(headerValue) }
if (disabled) {
xss.disable()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -37,6 +37,7 @@
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;
Expand All @@ -58,6 +59,7 @@
* @author Vedran Pavic
* @author Eleftheria Stein
* @author Marcus Da Coregio
* @author Daniel Garnier-Moiroux
*/
@ExtendWith(SpringTestContextExtension.class)
public class HeadersConfigurerTests {
Expand Down Expand Up @@ -171,6 +173,15 @@ public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssPr
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();
Expand All @@ -179,6 +190,15 @@ public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeade
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();
Expand Down Expand Up @@ -679,6 +699,22 @@ protected void configure(HttpSecurity http) throws Exception {

}

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

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

}

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

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,6 +31,7 @@
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;
Expand Down Expand Up @@ -273,8 +274,7 @@ protected void configure(HttpSecurity http) throws Exception {
// xss-protection@enabled and xss-protection@block
.defaultsDisabled()
.xssProtection()
.xssProtectionEnabled(true)
.block(false);
.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED);
// @formatter:on
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -296,6 +296,51 @@ public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten()
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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
}
}
}
}
}
}
Loading

0 comments on commit a5b23e6

Please sign in to comment.