From 5dab077b9bb7564f2c556b197c5c416c41783112 Mon Sep 17 00:00:00 2001 From: jahoti Date: Mon, 6 Sep 2021 00:00:00 +0000 Subject: Replace CSP filtering with blocking CSP headers are now blocked completely rather than modified. Also, filtering is applied whenever a payload is injected. --- background/policy_injector.js | 66 +++++++------------------------------------ common/misc.js | 13 ++++++++- content/main.js | 19 ++++++------- 3 files changed, 31 insertions(+), 67 deletions(-) diff --git a/background/policy_injector.js b/background/policy_injector.js index 72318d4..318190b 100644 --- a/background/policy_injector.js +++ b/background/policy_injector.js @@ -11,54 +11,24 @@ * IMPORT sign_data * IMPORT extract_signed * IMPORT sanitize_csp_header - * IMPORT csp_rule + * IMPORT make_csp_rule * IMPORT is_csp_header_name * IMPORTS_END */ function inject_csp_headers(headers, policy) { - let csp_headers; - let old_signature; - let hachette_header; + if (!policy.allow || policy.has_payload) { + /* Remove report-only CSP headers that snitch on us. */ + headers = headers.filter(h => !is_csp_header_name(h.name, true)); - for (const header of headers.filter(h => h.name === "x-hachette")) { - /* x-hachette header has format: _0_ */ - const match = /^([^_]+)_(0_.*)$/.exec(header.value); - if (!match) - continue; - - const result = extract_signed(...match.slice(1, 3)); - if (result.fail) - continue; - - /* This should succeed - it's our self-produced valid JSON. */ - const old_data = JSON.parse(decodeURIComponent(result.data)); - - /* Confirmed- it's the originals, smuggled in! */ - csp_headers = old_data.csp_headers; - old_signature = old_data.policy_sig; - - hachette_header = header; - break; - } - - if (!hachette_header) { - hachette_header = {name: "x-hachette"}; - headers.push(hachette_header); + /* Add our own CSP header */ + headers.push({ + name: "content-security-policy", + value: make_csp_rule(policy) + }); } - - csp_headers = csp_headers || - headers.filter(h => is_csp_header_name(h.name)); - - /* When blocking remove report-only CSP headers that snitch on us. */ - headers = headers.filter(h => !is_csp_header_name(h.name, !policy.allow)); - - if (old_signature) - headers = headers.filter(h => h.value.search(old_signature) === -1); - - headers.push(...csp_headers.map(h => sanitize_csp_header(h, policy))); - + const policy_str = encodeURIComponent(JSON.stringify(policy)); const signed_policy = sign_data(policy_str, new Date().getTime()); const later_30sec = new Date(new Date().getTime() + 30000).toGMTString(); @@ -67,22 +37,6 @@ function inject_csp_headers(headers, policy) value: `hachette-${signed_policy.join("=")}; Expires=${later_30sec};` }); - /* - * Smuggle in the signature and the original CSP headers for future use. - * These are signed with a time of 0, as it's not clear there is a limit on - * how long Firefox might retain headers in the cache. - */ - let hachette_data = {csp_headers, policy_sig: signed_policy[0]}; - hachette_data = encodeURIComponent(JSON.stringify(hachette_data)); - hachette_header.value = sign_data(hachette_data, 0).join("_"); - - /* To ensure there is a CSP header if required */ - if (!policy.allow) - headers.push({ - name: "content-security-policy", - value: csp_rule(policy.nonce) - }); - return headers; } diff --git a/common/misc.js b/common/misc.js index 91d60d2..97fc2dc 100644 --- a/common/misc.js +++ b/common/misc.js @@ -146,6 +146,17 @@ function sanitize_csp_header(header, policy) return {name: header.name, value: new_csp.join('')}; } +/* csp rule that blocks all scripts except for those injected by us */ +function make_csp_rule(policy) +{ + let rule = "prefetch-src 'none'; ", nonce = `'nonce-${policy.nonce}'`; + if (!policy.allow) { + rule += `script-src ${nonce}; script-src-elem ${nonce}; ` + + "script-src-attr 'none'; "; + } + return rule; +} + /* Regexes and objects to use as/in schemas for parse_json_with_schema(). */ const nonempty_string_matcher = /.+/; @@ -161,7 +172,7 @@ const matchers = { /* * EXPORTS_START * EXPORT gen_nonce - * EXPORT csp_rule + * EXPORT make_csp_rule * EXPORT is_csp_header_name * EXPORT nice_name * EXPORT open_in_settings diff --git a/content/main.js b/content/main.js index b2cc9ed..3ebf093 100644 --- a/content/main.js +++ b/content/main.js @@ -17,7 +17,7 @@ * IMPORT is_chrome * IMPORT is_mozilla * IMPORT start_activity_info_server - * IMPORT csp_rule + * IMPORT make_csp_rule * IMPORT is_csp_header_name * IMPORT sanitize_csp_header * IMPORTS_END @@ -175,9 +175,6 @@ function sanitize_meta(meta, policy) return; block_attribute(meta, "content"); - - if (is_csp_header_name(http_equiv, false)) - meta.content = sanitize_csp_header({value}, policy).value; } function sanitize_script(script) @@ -204,7 +201,7 @@ function apply_hachette_csp_rules(doc, policy) { const meta = doc.createElement("meta"); meta.setAttribute("http-equiv", "Content-Security-Policy"); - meta.setAttribute("content", csp_rule(policy.nonce)); + meta.setAttribute("content", make_csp_rule(policy)); doc.head.append(meta); /* CSP is already in effect, we can remove the now. */ meta.remove(); @@ -240,13 +237,15 @@ async function sanitize_document(doc, policy) for (const meta of old_html.querySelectorAll("head meta")) sanitize_meta(meta, policy); - for (const script of old_html.querySelectorAll("script")) - sanitize_script(script, policy); + if (!policy.allow) + for (const script of old_html.querySelectorAll("script")) + sanitize_script(script, policy); new_html.replaceWith(old_html); - for (const script of old_html.querySelectorAll("script")) - desanitize_script(script, policy); + if (!policy.allow) + for (const script of old_html.querySelectorAll("script")) + desanitize_script(script, policy); } if (!is_privileged_url(document.URL)) { @@ -282,7 +281,7 @@ if (!is_privileged_url(document.URL)) { } const doc_ready = Promise.all([ - policy.allow ? Promise.resolve : sanitize_document(document, policy), + (policy.allow && !policy.has_payload) ? Promise.resolve : sanitize_document(document, policy), new Promise(cb => document.addEventListener("DOMContentLoaded", cb, {once: true})) ]); -- cgit v1.2.3