Project

General

Profile

« Previous | Next » 

Revision 692577bb

Added by jahoti about 2 years ago

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.

View differences:

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