aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjahoti <jahoti@tilde.team>2021-07-16 00:00:00 +0000
committerjahoti <jahoti@tilde.team>2021-07-16 00:00:00 +0000
commit692577bbde5e8110855c022ec913324dfddce9ae (patch)
tree6cc013453cdac80fd427c63994f2f7cc019d9c42
parent0e002513d443ef7cddcc17acf178478844f609e9 (diff)
downloadbrowser-extension-692577bbde5e8110855c022ec913324dfddce9ae.tar.gz
browser-extension-692577bbde5e8110855c022ec913324dfddce9ae.zip
Use URL-based policy smuggling
Increase the power of URL-based smuggling by making it (effectively) compulsory in all cases and adapting a <salt><unique value><JSON-encoded settings> structure. While the details still need to be worked out, the potential for future expansion is there.
-rw-r--r--background/policy_injector.js52
-rw-r--r--common/misc.js36
-rw-r--r--content/main.js49
-rw-r--r--copyright2
4 files changed, 97 insertions, 42 deletions
diff --git a/background/policy_injector.js b/background/policy_injector.js
index eb67963..9e8ed61 100644
--- a/background/policy_injector.js
+++ b/background/policy_injector.js
@@ -2,6 +2,7 @@
* Myext injecting policy to page using webRequest
*
* Copyright (C) 2021 Wojtek Kosior
+ * Copyright (C) 2021 jahoti
* Redistribution terms are gathered in the `copyright' file.
*/
@@ -12,7 +13,9 @@
* IMPORT browser
* IMPORT is_chrome
* IMPORT gen_unique
+ * IMPORT gen_nonce
* IMPORT url_item
+ * IMPORT url_extract_policy
* IMPORT get_query_best
* IMPORT csp_rule
* IMPORTS_END
@@ -39,18 +42,46 @@ function is_our_header(header, rule)
return header.value === rule
}
-function inject(details)
+function url_inject(details)
{
- const url = url_item(details.url);
+ const targets = url_extract_policy(details.url);
+ if (targets.policy) {
+ return;
+ } else if (targets.signed) {
+ /* Redirect; update policy */
+ targets.target = targets.target2;
+ delete targets.target2
+ }
+
+ let redirect_url = targets.base_url + targets.sig;
+ let [pattern, settings] = query_best(targets.base_url);
+ if (!pattern)
+ /* Defaults */
+ settings = {};
+
+ const policy = {allow: settings.allow, nonce: gen_nonce()};
+
+ redirect_url += encodeURIComponent(JSON.stringify(policy));
+ if (targets.target)
+ redirect_url += targets.target;
+ if (targets.target2)
+ redirect_url += targets.target2;
+
+ return {redirectUrl: redirect_url};
+}
- const [pattern, settings] = query_best(url);
+function inject(details)
+{
+ const targets = url_extract_policy(details.url);
+ if (!targets.policy)
+ /* Block unsigned requests */
+ return {cancel: true};
- const nonce = gen_unique(url);
- const rule = csp_rule(nonce);
+ const rule = csp_rule(targets.policy.nonce);
var headers;
- if (settings !== undefined && settings.allow) {
+ if (targets.policy.allow) {
/*
* Chrome doesn't have the buggy behavior of repeatedly injecting a
* header we injected once. Firefox does and we have to remove it there.
@@ -80,6 +111,15 @@ 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(
inject,
{
diff --git a/common/misc.js b/common/misc.js
index 8b56e79..825a117 100644
--- a/common/misc.js
+++ b/common/misc.js
@@ -2,6 +2,7 @@
* Myext miscellaneous operations refactored to a separate file
*
* Copyright (C) 2021 Wojtek Kosior
+ * Copyright (C) 2021 jahoti
* Redistribution terms are gathered in the `copyright' file.
*/
@@ -14,6 +15,14 @@
* IMPORTS_END
*/
+/* Generate a random base64-encoded 128-bit sequence */
+function gen_nonce()
+{
+ let randomData = new Uint8Array(16);
+ crypto.getRandomValues(randomData);
+ return btoa(String.fromCharCode.apply(null, randomData));
+}
+
/*
* generating unique, per-site value that can be computed synchronously
* and is impossible to guess for a malicious website
@@ -26,9 +35,9 @@ function gen_unique(url)
function get_secure_salt()
{
if (is_chrome)
- return browser.runtime.getManifest().key.substring(0, 50);
+ return browser.runtime.getManifest().key.substring(0, 36);
else
- return browser.runtime.getURL("dummy");
+ return browser.runtime.getURL("dummy").substr(16, 36);
}
/*
@@ -95,11 +104,34 @@ function is_privileged_url(url)
return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url);
}
+/* Extract any policy present in the URL */
+function url_extract_policy(url)
+{
+ const targets = url_extract_target(url);
+ const key = '#' + get_secure_salt();
+ targets.sig = key + gen_unique(targets.base_url);
+
+ if (targets.target && targets.target.startsWith(key)) {
+ targets.signed = true;
+ if (targets.target.startsWith(targets.sig))
+ try {
+ const policy_string = targets.target.substring(101);
+ targets.policy = JSON.parse(decodeURIComponent(policy_string));
+ } catch (e) {
+ /* TODO what should happen here? */
+ }
+ }
+
+ return targets;
+}
+
/*
* EXPORTS_START
+ * EXPORT gen_nonce
* EXPORT gen_unique
* EXPORT url_item
* EXPORT url_extract_target
+ * EXPORT url_extract_policy
* EXPORT csp_rule
* EXPORT nice_name
* EXPORT open_in_settings
diff --git a/content/main.js b/content/main.js
index 9acf749..e75f61d 100644
--- a/content/main.js
+++ b/content/main.js
@@ -2,6 +2,7 @@
* Myext main content script run in all frames
*
* Copyright (C) 2021 Wojtek Kosior
+ * Copyright (C) 2021 jahoti
* Redistribution terms are gathered in the `copyright' file.
*/
@@ -10,7 +11,9 @@
* IMPORT handle_page_actions
* IMPORT url_item
* IMPORT url_extract_target
+ * IMPORT url_extract_policy
* IMPORT gen_unique
+ * IMPORT gen_nonce
* IMPORT csp_rule
* IMPORT is_privileged_url
* IMPORT sanitize_attributes
@@ -32,32 +35,6 @@
* urls has not yet been added to the extension.
*/
-let url = url_item(document.URL);
-let unique = gen_unique(url);
-
-
-function is_http()
-{
- return !!/^https?:\/\//i.exec(document.URL);
-}
-
-function is_whitelisted()
-{
- const parsed_url = url_extract_target(document.URL);
-
- if (parsed_url.target !== undefined &&
- parsed_url.target === '#' + unique) {
- if (parsed_url.target2 !== undefined)
- window.location.href = parsed_url.base_url + parsed_url.target2;
- else
- history.replaceState(null, "", parsed_url.base_url);
-
- return true;
- }
-
- return false;
-}
-
function handle_mutation(mutations, observer)
{
if (document.readyState === 'complete') {
@@ -113,7 +90,7 @@ function inject_csp(head)
let meta = document.createElement("meta");
meta.setAttribute("http-equiv", "Content-Security-Policy");
- meta.setAttribute("content", csp_rule(unique));
+ meta.setAttribute("content", csp_rule(nonce));
if (head.firstElementChild === null)
head.appendChild(meta);
@@ -122,14 +99,20 @@ function inject_csp(head)
}
if (!is_privileged_url(document.URL)) {
+ const targets = url_extract_policy(document.URL);
+ targets.policy = targets.policy || {};
+ const nonce = targets.policy.nonce || gen_nonce();
+
+ if (targets.signed)
+ if (targets.target2 !== undefined)
+ window.location.href = targets.base_url + targets.target2;
+ else
+ history.replaceState(null, "", targets.base_url);
+
start_activity_info_server();
- handle_page_actions(unique);
+ handle_page_actions(nonce);
- if (is_http()) {
- /* rely on CSP injected through webRequest */
- } else if (is_whitelisted()) {
- /* do not block scripts at all */
- } else {
+ if (!targets.policy.allow) {
block_nodes_recursively(document.documentElement);
if (is_chrome) {
diff --git a/copyright b/copyright
index a01c1fe..4ff7483 100644
--- a/copyright
+++ b/copyright
@@ -10,7 +10,7 @@ Files: build.sh
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
License: CC0
-Files: manifest.json
+Files: manifest.json background/policy_injector.js common/misc.js content/main.js
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
2021 jahoti <jahoti@tilde.team>
License: GPL-3+-javascript or Alicense-1.0