From 2900d3e3129114ffe5d03b75410decc3b44cc7ec Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Tue, 12 Apr 2022 20:43:27 -0500 Subject: [PATCH] Disable X-XSS-Protection filter, for safety Surprisingly, [`X-XSS-Protection` is safer when disabled][0], and browsers are dropping support for it as a result. Because it's less safe to enable the filter, this change sets the default (and only) value to `0`, instead of `1; mode=block`. This is a breaking change. See [issue #25][1]. [0]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection [1]: https://github.com/brokenhandsio/VaporSecurityHeaders/issues/25 --- README.md | 44 +++---------------- .../XSSProtectionConfiguration.swift | 25 +---------- .../SecurityHeaders.swift | 2 +- .../SecurityHeadersFactory.swift | 2 +- .../HeaderTests.swift | 42 ++++-------------- 5 files changed, 18 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index e4e26dd..7fe6c82 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ The default factory will add default values to your site for Content-Security-Po x-content-type-options: nosniff content-security-policy: default-src 'self' x-frame-options: DENY -x-xss-protection: 1; mode=block +x-xss-protection: 0 ``` ***Note:*** You should ensure you set the security headers as the first middleware in your `Middlewares` (i.e., the first middleware to be applied to responses) to make sure the headers get added to all responses. @@ -94,7 +94,7 @@ application.middleware.use(securityHeadersFactory.build()) x-content-type-options: nosniff content-security-policy: default-src 'none'; script-src https://static.brokenhands.io; x-frame-options: DENY -x-xss-protection: 1; mode=block +x-xss-protection: 0 ``` Each different header has its own configuration and options, details of which can be found below. @@ -114,7 +114,7 @@ application.middleware.use(securityHeaders.build()) x-content-type-options: nosniff content-security-policy: default-src 'none' x-frame-options: DENY -x-xss-protection: 1; mode=block +x-xss-protection: 0 ``` # Server Configuration @@ -317,44 +317,10 @@ The [above blog post](https://scotthelme.co.uk/content-security-policy-an-introd ## X-XSS-Protection -X-XSS-Protection configures the browser's cross-site scripting filter. The recommended, and default, setting is `.block` which blocks the response if the browser detects an attack. This can be configured with: - -```swift -let xssProtectionConfig = XSSProtectionConfiguration(option: .block) - -let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) -``` - -```http -x-xss-protection: 1; mode=block -``` - -To just enable the protection: - -```swift -let xssProtectionConfig = XSSProtectionConfiguration(option: .enable) -``` - -```http -x-xss-protection: 1 -``` - -To sanitize the page and report the violation: - -```swift -let xssProtectionConfig = XSSProtectionConfiguration(option: .report(uri: "https://report-uri.com")) - -let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) -``` - -```http -x-xss-protection: 1; report=https://report-uri.com -``` - -Or to disable: +X-XSS-Protection configures the browser's cross-site scripting filter. This package configures the header to be disabled, which (surprisingly) offers security benefits. See [this article on MDN for more information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection). ```swift -let xssProtectionConfig = XSSProtectionConfiguration(option: .disable) +let xssProtectionConfig = XSSProtectionConfiguration() let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) ``` diff --git a/Sources/VaporSecurityHeaders/Configurations/XSSProtectionConfiguration.swift b/Sources/VaporSecurityHeaders/Configurations/XSSProtectionConfiguration.swift index 1df2d66..4a86f3a 100644 --- a/Sources/VaporSecurityHeaders/Configurations/XSSProtectionConfiguration.swift +++ b/Sources/VaporSecurityHeaders/Configurations/XSSProtectionConfiguration.swift @@ -1,30 +1,9 @@ import Vapor public struct XSSProtectionConfiguration: SecurityHeaderConfiguration { - - public enum Options { - case disable - case enable - case block - case report(uri: String) - } - - private let option: Options - - public init(option: Options) { - self.option = option - } + public init () {} func setHeader(on response: Response, from request: Request) { - switch option { - case .disable: - response.headers.replaceOrAdd(name: .xssProtection, value: "0") - case .enable: - response.headers.replaceOrAdd(name: .xssProtection, value: "1") - case .block: - response.headers.replaceOrAdd(name: .xssProtection, value: "1; mode=block") - case .report(let uri): - response.headers.replaceOrAdd(name: .xssProtection, value: "1; report=\(uri)") - } + response.headers.replaceOrAdd(name: .xssProtection, value: "0") } } diff --git a/Sources/VaporSecurityHeaders/SecurityHeaders.swift b/Sources/VaporSecurityHeaders/SecurityHeaders.swift index 25a1139..a534209 100644 --- a/Sources/VaporSecurityHeaders/SecurityHeaders.swift +++ b/Sources/VaporSecurityHeaders/SecurityHeaders.swift @@ -7,7 +7,7 @@ public struct SecurityHeaders { init(contentTypeConfiguration: ContentTypeOptionsConfiguration = ContentTypeOptionsConfiguration(option: .nosniff), contentSecurityPolicyConfiguration: ContentSecurityPolicyConfiguration = ContentSecurityPolicyConfiguration(value: ContentSecurityPolicy().defaultSrc(sources: CSPKeywords.`self`)), frameOptionsConfiguration: FrameOptionsConfiguration = FrameOptionsConfiguration(option: .deny), - xssProtectionConfiguration: XSSProtectionConfiguration = XSSProtectionConfiguration(option: .block), + xssProtectionConfiguration: XSSProtectionConfiguration = XSSProtectionConfiguration(), hstsConfiguration: StrictTransportSecurityConfiguration? = nil, serverConfiguration: ServerConfiguration? = nil, contentSecurityPolicyReportOnlyConfiguration: ContentSecurityPolicyReportOnlyConfiguration? = nil, diff --git a/Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift b/Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift index 9d883b8..badc1bd 100644 --- a/Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift +++ b/Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift @@ -4,7 +4,7 @@ public class SecurityHeadersFactory { var contentTypeOptions = ContentTypeOptionsConfiguration(option: .nosniff) var contentSecurityPolicy = ContentSecurityPolicyConfiguration(value: ContentSecurityPolicy().defaultSrc(sources: CSPKeywords.`self`)) var frameOptions = FrameOptionsConfiguration(option: .deny) - var xssProtection = XSSProtectionConfiguration(option: .block) + var xssProtection = XSSProtectionConfiguration() var hsts: StrictTransportSecurityConfiguration? var server: ServerConfiguration? var referrerPolicy: ReferrerPolicyConfiguration? diff --git a/Tests/VaporSecurityHeadersTests/HeaderTests.swift b/Tests/VaporSecurityHeadersTests/HeaderTests.swift index 532f39a..9dd8371 100644 --- a/Tests/VaporSecurityHeadersTests/HeaderTests.swift +++ b/Tests/VaporSecurityHeadersTests/HeaderTests.swift @@ -37,7 +37,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'self'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory()) @@ -51,7 +51,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'self'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let expectedHSTSHeaderValue = "max-age=31536000; includeSubDomains; preload" let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory().with(strictTransportSecurity: StrictTransportSecurityConfiguration())) @@ -67,7 +67,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'none'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory.api()) @@ -81,7 +81,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'none'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let expectedHSTSHeaderValue = "max-age=31536000; includeSubDomains; preload" let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory.api().with(strictTransportSecurity: StrictTransportSecurityConfiguration())) @@ -133,38 +133,14 @@ class HeaderTests: XCTestCase { XCTAssertEqual("ALLOW-FROM https://test.com", response.headers[.xFrameOptions].first) } - func testHeaderWithXssProtectionDisable() throws { - let xssProtectionConfig = XSSProtectionConfiguration(option: .disable) + func testHeaderWithXssProtection() throws { + let xssProtectionConfig = XSSProtectionConfiguration() let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) let response = try makeTestResponse(for: request, securityHeadersToAdd: factory) XCTAssertEqual("0", response.headers[.xssProtection].first) } - func testHeaderWithXssProtectionEnable() throws { - let xssProtectionConfig = XSSProtectionConfiguration(option: .enable) - let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) - let response = try makeTestResponse(for: request, securityHeadersToAdd: factory) - - XCTAssertEqual("1", response.headers[.xssProtection].first) - } - - func testHeaderWithXssProtectionBlock() throws { - let xssProtectionConfig = XSSProtectionConfiguration(option: .block) - let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) - let response = try makeTestResponse(for: request, securityHeadersToAdd: factory) - - XCTAssertEqual("1; mode=block", response.headers[.xssProtection].first) - } - - func testHeaderWithXssProtectionReport() throws { - let xssProtectionConfig = XSSProtectionConfiguration(option: .report(uri: "https://test.com")) - let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig) - let response = try makeTestResponse(for: request, securityHeadersToAdd: factory) - - XCTAssertEqual("1; report=https://test.com", response.headers[.xssProtection].first) - } - func testHeaderWithHSTSwithMaxAge() throws { let hstsConfig = StrictTransportSecurityConfiguration(maxAge: 30) let factory = SecurityHeadersFactory().with(strictTransportSecurity: hstsConfig) @@ -591,7 +567,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'none'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let response = try makeTestResponse(for: abortRequest, securityHeadersToAdd: SecurityHeadersFactory.api()) @@ -605,7 +581,7 @@ class HeaderTests: XCTestCase { let expectedXCTOHeaderValue = "nosniff" let expectedCSPHeaderValue = "default-src 'none'" let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let response = try makeTestResponse(for: fileRequest, securityHeadersToAdd: SecurityHeadersFactory.api(), fileMiddleware: StubFileMiddleware()) XCTAssertEqual("Hello World!", String(data: response.body.data!, encoding: String.Encoding.utf8)) @@ -622,7 +598,7 @@ class HeaderTests: XCTestCase { .defaultSrc(sources: CSPKeywords.none) .scriptSrc(sources: "test") let expectedXFOHeaderValue = "DENY" - let expectedXSSProtectionHeaderValue = "1; mode=block" + let expectedXSSProtectionHeaderValue = "0" let response = try makeTestResponse(for: fileRequest, securityHeadersToAdd: SecurityHeadersFactory.api(), fileMiddleware: StubFileMiddleware(cspConfig: ContentSecurityPolicyConfiguration(value: csp)))