Project

General

Profile

« Previous | Next » 

Revision 704f2da0

Added by koszko almost 2 years ago

re-enable sanitizing of data: URLs and also sanitize intrinsics on non-HTML pages where CSP doesn't work

View differences:

content/freezer.js
1
/**
2
 * Helper functions for blocking scripts in pages, based off NoScript's lib/DocumentFreezer.js
3
 *
4
 * Copyright (C) 2005-2021 Giorgio Maone - https://maone.net
5
 * Copyright (C) 2021 jahoti
6
 * Redistribution terms are gathered in the `copyright' file.
7
 */
8

  
9
const loaderAttributes = ["href", "src", "data"];
10
const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i;
11

  
12
function sanitize_attributes(element) {
13
    if (element._frozen)
14
	return;
15
    let fa = [];
16
    let loaders = [];
17
    let attributes = element.attributes || [];
18

  
19
    for (let a of attributes) {
20
	let name = a.localName.toLowerCase();
21
	if (loaderAttributes.includes(name))
22
	    if (jsOrDataUrlRx.test(a.value))
23
		loaders.push(a);
24

  
25
	else if (name.startsWith("on")) {
26
	    console.debug("Removing", a, element.outerHTML);
27
	    fa.push(a.cloneNode());
28
	    a.value = "";
29
	    element[name] = null;
30
	}
31
    }
32
    if (loaders.length) {
33
	for (let a of loaders) {
34
	    fa.push(a.cloneNode());
35
	    a.value = "javascript://frozen";
36
	}
37
	if ("contentWindow" in element)
38
	    element.replaceWith(element = element.cloneNode(true));
39

  
40
    }
41
    if (fa.length)
42
	element._frozenAttributes = fa;
43
    element._frozen = true;
44
}
45

  
46
function mozilla_suppress_scripts(e) {
47
    if (document.readyState === 'complete') {
48
	removeEventListener('beforescriptexecute', blockExecute, true);
49
	console.log('Script suppressor has detached.');
50
	return;
51
    }
52
    console.log("script event", e);
53
    if (e.isTrusted && !e.target._hachette_payload) {
54
	e.preventDefault();
55
	console.log('Suppressed script', e.target);
56
    }
57
};
58

  
59
/*
60
 * EXPORTS_START
61
 * EXPORT mozilla_suppress_scripts
62
 * EXPORT sanitize_attributes
63
 * EXPORTS_END
64
 */
content/main.js
13 13
 * IMPORT sign_data
14 14
 * IMPORT gen_nonce
15 15
 * IMPORT is_privileged_url
16
 * IMPORT mozilla_suppress_scripts
17 16
 * IMPORT is_chrome
18 17
 * IMPORT is_mozilla
19 18
 * IMPORT start_activity_info_server
......
132 131
function _wait_for_head(doc, detached_html, callback)
133 132
{
134 133
    const waiting = {doc, detached_html, callback, observers: []};
135
    if (try_body_started(waiting))
136
	return;
137 134

  
138
    waiting.observers = [make_body_start_observer(detached_html, waiting)];
135
    /*
136
     * For XML and SVG documents, instead of waiting for `<head>', we wait
137
     * for the entire document to finish loading.
138
     */
139
    if (doc instanceof HTMLDocument) {
140
	if (try_body_started(waiting))
141
	    return;
142

  
143
	waiting.observers = [make_body_start_observer(detached_html, waiting)];
144
    }
145

  
139 146
    waiting.loaded_cb = () => finish_waiting(waiting);
140 147
    doc.addEventListener("DOMContentLoaded", waiting.loaded_cb);
141 148
}
......
200 207
    delete script.hachette_blocked_type;
201 208
}
202 209

  
203
function apply_hachette_csp_rules(doc, policy)
210
function apply_hachette_csp_rules(doc, head, policy)
204 211
{
205 212
    const meta = doc.createElement("meta");
206 213
    meta.setAttribute("http-equiv", "Content-Security-Policy");
207 214
    meta.setAttribute("content", csp_rule(policy.nonce));
208
    doc.head.append(meta);
215
    head.append(meta);
209 216
    /* CSP is already in effect, we can remove the <meta> now. */
210 217
    meta.remove();
211 218
}
212 219

  
220
function sanitize_urls(element)
221
{
222
    for (const attribute of [...element.attributes]) {
223
	if (/^(href|src|data)$/i.test(attribute.localName) &&
224
	    /^data:([^,;]*ml|unknown-content-type)/i.test(attribute.value))
225
	    block_attribute(element, attribute.localName);
226
    }
227
}
228

  
229
function start_data_urls_sanitizing(doc)
230
{
231
    doc.querySelectorAll("*[href], *[src], *[data]").forEach(sanitize_urls);
232
    const mutation_handler = m => m.addedNodes.forEach(sanitize_urls);
233
    const mo = new MutationObserver(ms => ms.forEach(mutation_handler));
234
    mo.observe(doc, {childList: true, subtree: true});
235
}
236

  
237
function apply_intrinsics_sanitizing(root_element)
238
{
239
    for (const subelem of root_element.querySelectorAll("*")) {
240
	[...subelem.attributes]
241
	    .filter(a => /^on/i.test(a.localName))
242
	    .filter(a => /^javascript:/i.test(a.value))
243
	    .forEach(a => block_attribute(subelem, a.localName));
244
    }
245
}
246

  
213 247
async function sanitize_document(doc, policy)
214 248
{
249
    /*
250
     * Blocking of scripts that are in the DOM from the beginning. Needed for
251
     * Mozilla, harmless on Chromium.
252
     * Note that at least in SVG documents the `src' attr on `<script>'s seems
253
     * to be ignored by Firefox, so we don't need to sanitize it.
254
     */
255
    for (const script of document.getElementsByTagName("script")) {
256
	const old_children = [...script.childNodes];
257
	script.innerHTML = "";
258
	setTimeout(() => old_children.forEach(c => script.append(c)), 0);
259
    }
260

  
215 261
    /*
216 262
     * Ensure our CSP rules are employed from the beginning. This CSP injection
217 263
     * method is, when possible, going to be applied together with CSP rules
218 264
     * injected using webRequest.
265
     * For non-HTML documents this is just a dummy operation of adding and
266
     * removing `head'.
219 267
     */
220
    const has_own_head = doc.head;
221
    if (!has_own_head)
222
	doc.documentElement.prepend(doc.createElement("head"));
268
    let added_head = doc.createElement("head");
269
    if (!doc.head)
270
	doc.documentElement.prepend(added_head);
223 271

  
224
    apply_hachette_csp_rules(doc, policy);
272
    apply_hachette_csp_rules(doc, added_head, policy);
225 273

  
226
    /* Probably not needed, but...: proceed with DOM in its initial state. */
227
    if (!has_own_head)
228
	doc.head.remove();
274
    /* Proceed with DOM in its initial state. */
275
    added_head.remove();
229 276

  
230 277
    /*
231 278
     * <html> node gets hijacked now, to be re-attached after <head> is loaded
......
243 290
    for (const script of old_html.querySelectorAll("script"))
244 291
	sanitize_script(script, policy);
245 292

  
293
    if (!(doc instanceof HTMLDocument))
294
	apply_intrinsics_sanitizing(old_html);
295

  
246 296
    new_html.replaceWith(old_html);
247 297

  
248 298
    for (const script of old_html.querySelectorAll("script"))
249 299
	desanitize_script(script, policy);
300

  
301
    start_data_urls_sanitizing(doc);
250 302
}
251 303

  
252 304
if (!is_privileged_url(document.URL)) {
copyright
67 67
 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
68 68
 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
69 69

  
70
Files: content/freezer.js
71
Copyright: 2005-2021 Giorgio Maone - https://maone.net
72
   2021 jahoti <jahoti@tilde.team>
73
License: GPL-2+
74

  
75 70
Files: licenses/*
76 71
Copyright: 2001, 2002, 2011-2013 Creative Commons
77 72
License: CC-BY-4.0

Also available in: Unified diff