Revision 692577bb
Added by jahoti about 2 years ago
| background/policy_injector.js | ||
|---|---|---|
| 2 | 2 |
* Myext injecting policy to page using webRequest |
| 3 | 3 |
* |
| 4 | 4 |
* Copyright (C) 2021 Wojtek Kosior |
| 5 |
* Copyright (C) 2021 jahoti |
|
| 5 | 6 |
* Redistribution terms are gathered in the `copyright' file. |
| 6 | 7 |
*/ |
| 7 | 8 |
|
| ... | ... | |
| 12 | 13 |
* IMPORT browser |
| 13 | 14 |
* IMPORT is_chrome |
| 14 | 15 |
* IMPORT gen_unique |
| 16 |
* IMPORT gen_nonce |
|
| 15 | 17 |
* IMPORT url_item |
| 18 |
* IMPORT url_extract_policy |
|
| 16 | 19 |
* IMPORT get_query_best |
| 17 | 20 |
* IMPORT csp_rule |
| 18 | 21 |
* IMPORTS_END |
| ... | ... | |
| 39 | 42 |
return header.value === rule |
| 40 | 43 |
} |
| 41 | 44 |
|
| 42 |
function inject(details) |
|
| 45 |
function url_inject(details)
|
|
| 43 | 46 |
{
|
| 44 |
const url = url_item(details.url); |
|
| 47 |
const targets = url_extract_policy(details.url); |
|
| 48 |
if (targets.policy) {
|
|
| 49 |
return; |
|
| 50 |
} else if (targets.signed) {
|
|
| 51 |
/* Redirect; update policy */ |
|
| 52 |
targets.target = targets.target2; |
|
| 53 |
delete targets.target2 |
|
| 54 |
} |
|
| 55 |
|
|
| 56 |
let redirect_url = targets.base_url + targets.sig; |
|
| 57 |
let [pattern, settings] = query_best(targets.base_url); |
|
| 58 |
if (!pattern) |
|
| 59 |
/* Defaults */ |
|
| 60 |
settings = {};
|
|
| 61 |
|
|
| 62 |
const policy = {allow: settings.allow, nonce: gen_nonce()};
|
|
| 63 |
|
|
| 64 |
redirect_url += encodeURIComponent(JSON.stringify(policy)); |
|
| 65 |
if (targets.target) |
|
| 66 |
redirect_url += targets.target; |
|
| 67 |
if (targets.target2) |
|
| 68 |
redirect_url += targets.target2; |
|
| 69 |
|
|
| 70 |
return {redirectUrl: redirect_url};
|
|
| 71 |
} |
|
| 45 | 72 |
|
| 46 |
const [pattern, settings] = query_best(url); |
|
| 73 |
function inject(details) |
|
| 74 |
{
|
|
| 75 |
const targets = url_extract_policy(details.url); |
|
| 76 |
if (!targets.policy) |
|
| 77 |
/* Block unsigned requests */ |
|
| 78 |
return {cancel: true};
|
|
| 47 | 79 |
|
| 48 |
const nonce = gen_unique(url); |
|
| 49 |
const rule = csp_rule(nonce); |
|
| 80 |
const rule = csp_rule(targets.policy.nonce); |
|
| 50 | 81 |
|
| 51 | 82 |
var headers; |
| 52 | 83 |
|
| 53 |
if (settings !== undefined && settings.allow) {
|
|
| 84 |
if (targets.policy.allow) {
|
|
| 54 | 85 |
/* |
| 55 | 86 |
* Chrome doesn't have the buggy behavior of repeatedly injecting a |
| 56 | 87 |
* header we injected once. Firefox does and we have to remove it there. |
| ... | ... | |
| 80 | 111 |
if (is_chrome) |
| 81 | 112 |
extra_opts.push("extraHeaders");
|
| 82 | 113 |
|
| 114 |
browser.webRequest.onBeforeRequest.addListener( |
|
| 115 |
url_inject, |
|
| 116 |
{
|
|
| 117 |
urls: ["<all_urls>"], |
|
| 118 |
types: ["main_frame", "sub_frame"] |
|
| 119 |
}, |
|
| 120 |
["blocking"] |
|
| 121 |
); |
|
| 122 |
|
|
| 83 | 123 |
browser.webRequest.onHeadersReceived.addListener( |
| 84 | 124 |
inject, |
| 85 | 125 |
{
|
| common/misc.js | ||
|---|---|---|
| 2 | 2 |
* Myext miscellaneous operations refactored to a separate file |
| 3 | 3 |
* |
| 4 | 4 |
* Copyright (C) 2021 Wojtek Kosior |
| 5 |
* Copyright (C) 2021 jahoti |
|
| 5 | 6 |
* Redistribution terms are gathered in the `copyright' file. |
| 6 | 7 |
*/ |
| 7 | 8 |
|
| ... | ... | |
| 14 | 15 |
* IMPORTS_END |
| 15 | 16 |
*/ |
| 16 | 17 |
|
| 18 |
/* Generate a random base64-encoded 128-bit sequence */ |
|
| 19 |
function gen_nonce() |
|
| 20 |
{
|
|
| 21 |
let randomData = new Uint8Array(16); |
|
| 22 |
crypto.getRandomValues(randomData); |
|
| 23 |
return btoa(String.fromCharCode.apply(null, randomData)); |
|
| 24 |
} |
|
| 25 |
|
|
| 17 | 26 |
/* |
| 18 | 27 |
* generating unique, per-site value that can be computed synchronously |
| 19 | 28 |
* and is impossible to guess for a malicious website |
| ... | ... | |
| 26 | 35 |
function get_secure_salt() |
| 27 | 36 |
{
|
| 28 | 37 |
if (is_chrome) |
| 29 |
return browser.runtime.getManifest().key.substring(0, 50);
|
|
| 38 |
return browser.runtime.getManifest().key.substring(0, 36);
|
|
| 30 | 39 |
else |
| 31 |
return browser.runtime.getURL("dummy");
|
|
| 40 |
return browser.runtime.getURL("dummy").substr(16, 36);
|
|
| 32 | 41 |
} |
| 33 | 42 |
|
| 34 | 43 |
/* |
| ... | ... | |
| 95 | 104 |
return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url); |
| 96 | 105 |
} |
| 97 | 106 |
|
| 107 |
/* Extract any policy present in the URL */ |
|
| 108 |
function url_extract_policy(url) |
|
| 109 |
{
|
|
| 110 |
const targets = url_extract_target(url); |
|
| 111 |
const key = '#' + get_secure_salt(); |
|
| 112 |
targets.sig = key + gen_unique(targets.base_url); |
|
| 113 |
|
|
| 114 |
if (targets.target && targets.target.startsWith(key)) {
|
|
| 115 |
targets.signed = true; |
|
| 116 |
if (targets.target.startsWith(targets.sig)) |
|
| 117 |
try {
|
|
| 118 |
const policy_string = targets.target.substring(101); |
|
| 119 |
targets.policy = JSON.parse(decodeURIComponent(policy_string)); |
|
| 120 |
} catch (e) {
|
|
| 121 |
/* TODO what should happen here? */ |
|
| 122 |
} |
|
| 123 |
} |
|
| 124 |
|
|
| 125 |
return targets; |
|
| 126 |
} |
|
| 127 |
|
|
| 98 | 128 |
/* |
| 99 | 129 |
* EXPORTS_START |
| 130 |
* EXPORT gen_nonce |
|
| 100 | 131 |
* EXPORT gen_unique |
| 101 | 132 |
* EXPORT url_item |
| 102 | 133 |
* EXPORT url_extract_target |
| 134 |
* EXPORT url_extract_policy |
|
| 103 | 135 |
* EXPORT csp_rule |
| 104 | 136 |
* EXPORT nice_name |
| 105 | 137 |
* EXPORT open_in_settings |
| content/main.js | ||
|---|---|---|
| 2 | 2 |
* Myext main content script run in all frames |
| 3 | 3 |
* |
| 4 | 4 |
* Copyright (C) 2021 Wojtek Kosior |
| 5 |
* Copyright (C) 2021 jahoti |
|
| 5 | 6 |
* Redistribution terms are gathered in the `copyright' file. |
| 6 | 7 |
*/ |
| 7 | 8 |
|
| ... | ... | |
| 10 | 11 |
* IMPORT handle_page_actions |
| 11 | 12 |
* IMPORT url_item |
| 12 | 13 |
* IMPORT url_extract_target |
| 14 |
* IMPORT url_extract_policy |
|
| 13 | 15 |
* IMPORT gen_unique |
| 16 |
* IMPORT gen_nonce |
|
| 14 | 17 |
* IMPORT csp_rule |
| 15 | 18 |
* IMPORT is_privileged_url |
| 16 | 19 |
* IMPORT sanitize_attributes |
| ... | ... | |
| 32 | 35 |
* urls has not yet been added to the extension. |
| 33 | 36 |
*/ |
| 34 | 37 |
|
| 35 |
let url = url_item(document.URL); |
|
| 36 |
let unique = gen_unique(url); |
|
| 37 |
|
|
| 38 |
|
|
| 39 |
function is_http() |
|
| 40 |
{
|
|
| 41 |
return !!/^https?:\/\//i.exec(document.URL); |
|
| 42 |
} |
|
| 43 |
|
|
| 44 |
function is_whitelisted() |
|
| 45 |
{
|
|
| 46 |
const parsed_url = url_extract_target(document.URL); |
|
| 47 |
|
|
| 48 |
if (parsed_url.target !== undefined && |
|
| 49 |
parsed_url.target === '#' + unique) {
|
|
| 50 |
if (parsed_url.target2 !== undefined) |
|
| 51 |
window.location.href = parsed_url.base_url + parsed_url.target2; |
|
| 52 |
else |
|
| 53 |
history.replaceState(null, "", parsed_url.base_url); |
|
| 54 |
|
|
| 55 |
return true; |
|
| 56 |
} |
|
| 57 |
|
|
| 58 |
return false; |
|
| 59 |
} |
|
| 60 |
|
|
| 61 | 38 |
function handle_mutation(mutations, observer) |
| 62 | 39 |
{
|
| 63 | 40 |
if (document.readyState === 'complete') {
|
| ... | ... | |
| 113 | 90 |
|
| 114 | 91 |
let meta = document.createElement("meta");
|
| 115 | 92 |
meta.setAttribute("http-equiv", "Content-Security-Policy");
|
| 116 |
meta.setAttribute("content", csp_rule(unique));
|
|
| 93 |
meta.setAttribute("content", csp_rule(nonce));
|
|
| 117 | 94 |
|
| 118 | 95 |
if (head.firstElementChild === null) |
| 119 | 96 |
head.appendChild(meta); |
| ... | ... | |
| 122 | 99 |
} |
| 123 | 100 |
|
| 124 | 101 |
if (!is_privileged_url(document.URL)) {
|
| 102 |
const targets = url_extract_policy(document.URL); |
|
| 103 |
targets.policy = targets.policy || {};
|
|
| 104 |
const nonce = targets.policy.nonce || gen_nonce(); |
|
| 105 |
|
|
| 106 |
if (targets.signed) |
|
| 107 |
if (targets.target2 !== undefined) |
|
| 108 |
window.location.href = targets.base_url + targets.target2; |
|
| 109 |
else |
|
| 110 |
history.replaceState(null, "", targets.base_url); |
|
| 111 |
|
|
| 125 | 112 |
start_activity_info_server(); |
| 126 |
handle_page_actions(unique);
|
|
| 113 |
handle_page_actions(nonce);
|
|
| 127 | 114 |
|
| 128 |
if (is_http()) {
|
|
| 129 |
/* rely on CSP injected through webRequest */ |
|
| 130 |
} else if (is_whitelisted()) {
|
|
| 131 |
/* do not block scripts at all */ |
|
| 132 |
} else {
|
|
| 115 |
if (!targets.policy.allow) {
|
|
| 133 | 116 |
block_nodes_recursively(document.documentElement); |
| 134 | 117 |
|
| 135 | 118 |
if (is_chrome) {
|
| copyright | ||
|---|---|---|
| 10 | 10 |
Copyright: 2021 Wojtek Kosior <koszko@koszko.org> |
| 11 | 11 |
License: CC0 |
| 12 | 12 |
|
| 13 |
Files: manifest.json |
|
| 13 |
Files: manifest.json background/policy_injector.js common/misc.js content/main.js
|
|
| 14 | 14 |
Copyright: 2021 Wojtek Kosior <koszko@koszko.org> |
| 15 | 15 |
2021 jahoti <jahoti@tilde.team> |
| 16 | 16 |
License: GPL-3+-javascript or Alicense-1.0 |
Also available in: Unified diff
Use URL-based policy smuggling
Increase the power of URL-based smuggling by making it (effectively)
compulsory in all cases and adapting a structure. While the details still need to be worked out, the
potential for future expansion is there.