From 4b70c9f89d10a3bcf96d8fa349c8fa80baa4d3de Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Sat, 2 Jun 2018 21:29:36 +0200 Subject: [PATCH] Add referrer policy setup check Fixes #9122 Based on https://www.w3.org/TR/referrer-policy/ and https://scotthelme.co.uk/a-new-security-header-referrer-policy/ Setting a sane Referrer-Policy will tell the browser if/when to send referrer headers when accessing a link from Nextcloud. When configured properly this results in less tracking and less leaking of (possibly) sensitive urls * Fix tests Signed-off-by: Roeland Jago Douma --- core/js/setupchecks.js | 19 +++ core/js/tests/specs/setupchecksSpec.js | 211 ++++++++++++++++++++++++- 2 files changed, 226 insertions(+), 4 deletions(-) diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index af769dd9b7..a2a7508693 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -283,6 +283,25 @@ }); } } + + if (!xhr.getResponseHeader('Referrer-Policy') || + (xhr.getResponseHeader('Referrer-Policy').toLowerCase() !== 'no-referrer' && + xhr.getResponseHeader('Referrer-Policy').toLowerCase() !== 'no-referrer-when-downgrade' && + xhr.getResponseHeader('Referrer-Policy').toLowerCase() !== 'strict-origin' && + xhr.getResponseHeader('Referrer-Policy').toLowerCase() !== 'strict-origin-when-cross-origin')) { + messages.push({ + msg: t('core', 'The "{header}" HTTP header is not set to "{val1}", "{val2}", "{val3}" or "{val4}". This can leak referer information. See the W3C Recommendation.', + { + header: 'Referrer-Policy', + val1: 'no-referrer', + val2: 'no-referrer-when-downgrade', + val3: 'strict-origin', + val4: 'strict-origin-when-cross-origin', + link: 'https://www.w3.org/TR/referrer-policy/' + }), + type: OC.SetupChecks.MESSAGE_TYPE_INFO + }); + } } else { messages.push({ msg: t('core', 'Error occurred while checking server setup'), diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index aab72bbc59..feef66c353 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -114,7 +114,7 @@ describe('OC.SetupChecks tests', function() { done(); }); }); - + it('should not return an error if data directory is protected', function(done) { var async = OC.SetupChecks.checkDataProtected(); @@ -495,7 +495,7 @@ describe('OC.SetupChecks tests', function() { async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'Error occurred while checking server setup', + msg: 'Error occurred while checking server setup', type: OC.SetupChecks.MESSAGE_TYPE_ERROR },{ msg: 'Error occurred while checking server setup', @@ -538,7 +538,10 @@ describe('OC.SetupChecks tests', function() { }, { msg: 'The "X-Permitted-Cross-Domain-Policies" HTTP header is not set to "none". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING - }, + }, { + msg: 'The "Referrer-Policy" HTTP header is not set to "no-referrer", "no-referrer-when-downgrade", "strict-origin" or "strict-origin-when-cross-origin". This can leak referer information. See the W3C Recommendation.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO + } ]); done(); }); @@ -556,6 +559,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000;preload', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', } ); @@ -585,6 +589,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer' } ); @@ -593,6 +598,196 @@ describe('OC.SetupChecks tests', function() { done(); }); }); + + describe('check Referrer-Policy header', function() { + it('should return no message if Referrer-Policy is set to no-referrer', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([]); + done(); + }); + }); + + it('should return no message if Referrer-Policy is set to no-referrer-when-downgrade', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer-when-downgrade', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([]); + done(); + }); + }); + + it('should return no message if Referrer-Policy is set to strict-origin', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'strict-origin', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([]); + done(); + }); + }); + + it('should return no message if Referrer-Policy is set to strict-origin-when-cross-origin', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([]); + done(); + }); + }); + + it('should return a message if Referrer-Policy is set to same-origin', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'same-origin', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([ + { + msg: 'The "Referrer-Policy" HTTP header is not set to "no-referrer", "no-referrer-when-downgrade", "strict-origin" or "strict-origin-when-cross-origin". This can leak referer information. See the W3C Recommendation.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO + } + ]); + done(); + }); + }); + + it('should return a message if Referrer-Policy is set to origin', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'origin', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([ + { + msg: 'The "Referrer-Policy" HTTP header is not set to "no-referrer", "no-referrer-when-downgrade", "strict-origin" or "strict-origin-when-cross-origin". This can leak referer information. See the W3C Recommendation.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO + } + ]); + done(); + }); + }); + + it('should return a message if Referrer-Policy is set to origin-when-cross-origin', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'origin-when-cross-origin', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([ + { + msg: 'The "Referrer-Policy" HTTP header is not set to "no-referrer", "no-referrer-when-downgrade", "strict-origin" or "strict-origin-when-cross-origin". This can leak referer information. See the W3C Recommendation.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO + } + ]); + done(); + }); + }); + + it('should return a message if Referrer-Policy is set to unsafe-url', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Download-Options': 'noopen', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'unsafe-url', + }); + + result.done(function( data, s, x ){ + expect(data).toEqual([ + { + msg: 'The "Referrer-Policy" HTTP header is not set to "no-referrer", "no-referrer-when-downgrade", "strict-origin" or "strict-origin-when-cross-origin". This can leak referer information. See the W3C Recommendation.', + type: OC.SetupChecks.MESSAGE_TYPE_INFO + } + ]); + done(); + }); + }); + }); }); it('should return a SSL warning if HTTPS is not used', function(done) { @@ -607,6 +802,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', } ); @@ -631,7 +827,7 @@ describe('OC.SetupChecks tests', function() { ); async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'Error occurred while checking server setup', + msg: 'Error occurred while checking server setup', type: OC.SetupChecks.MESSAGE_TYPE_ERROR }, { msg: 'Error occurred while checking server setup', @@ -653,6 +849,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', } ); @@ -678,6 +875,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', } ); @@ -703,6 +901,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', } ); @@ -727,6 +926,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', }); async.done(function( data, s, x ){ @@ -747,6 +947,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', }); async.done(function( data, s, x ){ @@ -767,6 +968,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', }); async.done(function( data, s, x ){ @@ -787,6 +989,7 @@ describe('OC.SetupChecks tests', function() { 'X-Frame-Options': 'SAMEORIGIN', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', }); async.done(function( data, s, x ){