aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2021-08-18 17:53:57 +0200
committerWojtek Kosior <koszko@koszko.org>2021-08-18 17:53:57 +0200
commit014f2a2f4e2071c35314d67285711f0f4615266b (patch)
tree081c18c6fc1270d1e312962bd21b71a7072004c4 /background
parent0bbda8fceb52f28032460db0331b09ad086a2a64 (diff)
downloadbrowser-extension-014f2a2f4e2071c35314d67285711f0f4615266b.tar.gz
browser-extension-014f2a2f4e2071c35314d67285711f0f4615266b.zip
implement smuggling via cookies instead of URL
Diffstat (limited to 'background')
-rw-r--r--background/policy_injector.js190
1 files changed, 65 insertions, 125 deletions
diff --git a/background/policy_injector.js b/background/policy_injector.js
index 9725e99..947812e 100644
--- a/background/policy_injector.js
+++ b/background/policy_injector.js
@@ -8,19 +8,16 @@
/*
* IMPORTS_START
- * IMPORT TYPE_PREFIX
* IMPORT get_storage
* IMPORT browser
* IMPORT is_chrome
- * IMPORT is_mozilla
- * IMPORT gen_unique
* IMPORT gen_nonce
* IMPORT is_privileged_url
- * IMPORT url_item
- * IMPORT url_extract_target
- * IMPORT sign_policy
+ * IMPORT sign_data
+ * IMPORT extract_signed
* IMPORT query_best
* IMPORT sanitize_csp_header
+ * IMPORT csp_rule
* IMPORTS_END
*/
@@ -32,129 +29,81 @@ const csp_header_names = new Set([
"x-content-security-policy"
]);
-/* TODO: variable no longer in use; remove if not needed */
-const unwanted_csp_directives = new Set([
- "report-to",
- "report-uri",
- "script-src",
- "script-src-elem",
- "prefetch-src"
-]);
-
const report_only = "content-security-policy-report-only";
-function url_inject(details)
+function headers_inject(details)
{
- if (is_privileged_url(details.url))
+ console.log("ijnector details", details);
+ const url = details.url;
+ if (is_privileged_url(url))
return;
- const targets = url_extract_target(details.url);
- if (targets.current)
- return;
+ const [pattern, settings] = query_best(storage, url);
+ const allow = !!(settings && settings.allow);
+ const nonce = gen_nonce();
+ const rule = `'nonce-${nonce}'`;
- /* Redirect; update policy */
- if (targets.policy)
- targets.target = "";
-
- let [pattern, settings] = query_best(storage, targets.base_url);
- /* Defaults */
- if (!pattern)
- settings = {};
-
- const policy = encodeURIComponent(
- JSON.stringify({
- allow: settings.allow,
- nonce: gen_nonce(),
- base_url: targets.base_url
- })
- );
+ let orig_csp_headers;
+ let old_signature;
+ let hachette_header;
+ let headers = details.responseHeaders;
- return {
- redirectUrl: [
- targets.base_url,
- '#', sign_policy(policy, new Date()), policy,
- targets.target,
- targets.target2
- ].join("")
- };
-}
+ for (const header of headers.filter(h => h.name === "x-hachette")) {
+ const match = /^([^%])(%.*)$/.exec(header.value);
+ if (!match)
+ continue;
-function headers_inject(details)
-{
- const targets = url_extract_target(details.url);
- /* Block mis-/unsigned requests */
- if (!targets.current)
- return {cancel: true};
-
- let orig_csp_headers = is_chrome ? null : [];
- let headers = [];
- let csp_headers = is_chrome ? headers : [];
-
- const rule = `'nonce-${targets.policy.nonce}'`;
- const block = !targets.policy.allow;
-
- for (const header of details.responseHeaders) {
- if (!csp_header_names.has(header)) {
- /* Remove headers that only snitch on us */
- if (header.name.toLowerCase() === report_only && block)
- continue;
- headers.push(header);
-
- /* If these are the original CSP headers, use them instead */
- /* Test based on url_extract_target() in misc.js */
- if (is_mozilla && header.name === "x-orig-csp") {
- let index = header.value.indexOf('%5B');
- if (index === -1)
- continue;
-
- let sig = header.value.substring(0, index);
- let data = header.value.substring(index);
- if (sig !== sign_policy(data, 0))
- continue;
-
- /* Confirmed- it's the originals, smuggled in! */
- try {
- data = JSON.parse(decodeURIComponent(data));
- } catch (e) {
- /* This should not be reached -
- it's our self-produced valid JSON. */
- console.log("Unexpected internal error - invalid JSON smuggled!", e);
- }
-
- orig_csp_headers = csp_headers = null;
- for (const header of data)
- headers.push(sanitize_csp_header(header, rule, block));
- }
- } else if (is_chrome || !orig_csp_headers) {
- csp_headers.push(sanitize_csp_header(header, rule, block));
- if (is_mozilla)
- orig_csp_headers.push(header);
- }
+ const old_data = extract_signed(...match.splice(1, 2), [[0]]);
+ if (!old_data || old_data.url !== url)
+ continue;
+
+ /* Confirmed- it's the originals, smuggled in! */
+ orig_csp_headers = old_data.csp_headers;
+ old_signature = old_data.policy_signature;
+
+ hachette_header = header;
+ break;
}
- if (orig_csp_headers) {
- /** Smuggle in 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 these headers in
- * the cache.
- */
- orig_csp_headers = encodeURIComponent(JSON.stringify(orig_csp_headers));
- headers.push({
- name: "x-orig-csp",
- value: sign_policy(orig_csp_headers, 0) + orig_csp_headers
- });
-
- headers = headers.concat(csp_headers);
+ if (!hachette_header) {
+ hachette_header = {name: "x-hachette"};
+ headers.push(hachette_header);
}
+ orig_csp_headers ||=
+ headers.filter(h => csp_header_names.has(h.name.toLowerCase()));
+ headers = headers.filter(h => !csp_header_names.has(h.name.toLowerCase()));
+
+ /* Remove headers that only snitch on us */
+ if (!allow)
+ headers = headers.filter(h => h.name.toLowerCase() !== report_only);
+
+ if (old_signature)
+ headers = headers.filter(h => h.name.search(old_signature) === -1);
+
+ const sanitizer = h => sanitize_csp_header(h, rule, allow);
+ headers.push(...orig_csp_headers.map(sanitizer));
+
+ const policy = encodeURIComponent(JSON.stringify({allow, nonce, url}));
+ const policy_signature = sign_data(policy, new Date());
+ const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
+ headers.push({
+ name: "Set-Cookie",
+ value: `hachette-${policy_signature}=${policy}; 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: orig_csp_headers, policy_signature, url};
+ hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
+ hachette_header.value = sign_data(hachette_data, 0) + hachette_data;
+
/* To ensure there is a CSP header if required */
- if (block) {
- headers.push({
- name: "content-security-policy",
- value: `script-src ${rule}; script-src-elem ${rule}; ` +
- "script-src-attr 'none'; prefetch-src 'none';"
- });
- }
+ if (!allow)
+ headers.push({name: "content-security-policy", value: csp_rule(nonce)});
return {responseHeaders: headers};
}
@@ -167,15 +116,6 @@ async function start_policy_injector()
if (is_chrome)
extra_opts.push("extraHeaders");
- browser.webRequest.onBeforeRequest.addListener(
- url_inject,
- {
- urls: ["<all_urls>"],
- types: ["main_frame", "sub_frame"]
- },
- ["blocking"]
- );
-
browser.webRequest.onHeadersReceived.addListener(
headers_inject,
{