From fba4820bec6714115ef03bd4bdfd714ba485ac2c Mon Sep 17 00:00:00 2001 From: jahoti Date: Wed, 21 Jul 2021 00:00:00 +0000 Subject: [UNTESTED- will test] Use more nuanced CSP filtering CSP headers are now parsed and processed, rather than treated as simple units. This allows us to ensure policies delivered as HTTP headers do not interfere with our script filtering, as well as to preserve useful protections while removing the ones that could be problematic. Additionally, prefetching should now be blocked on pages where native scripts aren't allowed, and all reporting of CSP violations has been stripped (is this appropriate?). --- background/policy_injector.js | 72 +++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/background/policy_injector.js b/background/policy_injector.js index 9a994f8..a67b4e3 100644 --- a/background/policy_injector.js +++ b/background/policy_injector.js @@ -12,14 +12,13 @@ * IMPORT get_storage * IMPORT browser * IMPORT is_chrome - * IMPORT is_mozilla * IMPORT gen_unique * IMPORT gen_nonce * IMPORT is_privileged_url * IMPORT url_extract_target * IMPORT sign_policy * IMPORT get_query_best - * IMPORT csp_rule + * IMPORT parse_csp * IMPORTS_END */ @@ -32,11 +31,19 @@ const csp_header_names = { "x-content-security-policy" : true }; +const unwanted_csp_directives = { + "report-to" : true, + "report-uri" : true, + "script-src" : true, + "script-src-elem" : true, + "prefetch-src": true +}; + const header_name = "content-security-policy"; -function is_csp_header(header) +function not_csp_header(header) { - return !!csp_header_names[header.name.toLowerCase()]; + return !csp_header_names[header.name.toLowerCase()]; } function url_inject(details) @@ -82,21 +89,48 @@ function headers_inject(details) if (!targets.current) return {cancel: true}; - const rule = csp_rule(targets.policy.nonce); - var headers = details.responseHeaders; - - /* - * Chrome doesn't have the buggy behavior of caching headers - * we injected. Firefox does and we have to remove it there. - */ - if (!targets.policy.allow || is_mozilla) - headers = headers.filter(h => !is_csp_header(h)); - - if (!targets.policy.allow) { - headers.push({ - name : header_name, - value : rule - }); + const headers = []; + for (let header of details.responseHeaders) { + if (not_csp_header(header)) { + /* Retain all non-snitching headers */ + if (header.name.toLowerCase() !== + 'content-security-policy-report-only') + headers.push(header); + + continue; + } + + const csp = parse_csp(header.value); + const rule = `'nonce-${targets.policy.nonce}'` + + /* TODO: confirm deleting non-existent things is OK everywhere */ + /* No snitching or prefetching/rendering */ + delete csp['report-to']; + delete csp['report-uri']; + + if (!target.policy.allow) { + delete csp['script-src']; + delete csp['script-src-elem']; + csp['script-src-attr'] = ["'none'"]; + csp['prefetch-src'] = ["'none'"]; + } + + if ('script-src' in csp) + csp['script-src'].push(rule); + else + csp['script-src'] = rule; + + if ('script-src-elem' in csp) + csp['script-src-elem'].push(rule); + else + csp['script-src-elem'] = rule; + + /* TODO: is this safe */ + let new_policy = Object.entries(csp).map( + i => i[0] + ' ' + i[1].join(' ') + ';' + ); + + headers.push({name: header.name, value: new_policy.join('')}); } return {responseHeaders: headers}; -- cgit v1.2.3