diff options
author | Wojtek Kosior <koszko@koszko.org> | 2021-09-06 20:45:50 +0200 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2021-09-06 20:45:50 +0200 |
commit | 704f2da0673dc714f72b9bb82f6bf648795d4335 (patch) | |
tree | 15d4819bef4c984b6494bbf4d188d42d285352cb /content | |
parent | ed08ef1a6df1713a0e00ccd656f4bb4ed44647a4 (diff) | |
download | browser-extension-704f2da0673dc714f72b9bb82f6bf648795d4335.tar.gz browser-extension-704f2da0673dc714f72b9bb82f6bf648795d4335.zip |
re-enable sanitizing of data: URLs and also sanitize intrinsics on non-HTML pages where CSP doesn't work
Diffstat (limited to 'content')
-rw-r--r-- | content/freezer.js | 64 | ||||
-rw-r--r-- | content/main.js | 78 |
2 files changed, 65 insertions, 77 deletions
diff --git a/content/freezer.js b/content/freezer.js deleted file mode 100644 index 0ea362e..0000000 --- a/content/freezer.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Helper functions for blocking scripts in pages, based off NoScript's lib/DocumentFreezer.js - * - * Copyright (C) 2005-2021 Giorgio Maone - https://maone.net - * Copyright (C) 2021 jahoti - * Redistribution terms are gathered in the `copyright' file. - */ - -const loaderAttributes = ["href", "src", "data"]; -const jsOrDataUrlRx = /^(?:data:(?:[^,;]*ml|unknown-content-type)|javascript:)/i; - -function sanitize_attributes(element) { - if (element._frozen) - return; - let fa = []; - let loaders = []; - let attributes = element.attributes || []; - - for (let a of attributes) { - let name = a.localName.toLowerCase(); - if (loaderAttributes.includes(name)) - if (jsOrDataUrlRx.test(a.value)) - loaders.push(a); - - else if (name.startsWith("on")) { - console.debug("Removing", a, element.outerHTML); - fa.push(a.cloneNode()); - a.value = ""; - element[name] = null; - } - } - if (loaders.length) { - for (let a of loaders) { - fa.push(a.cloneNode()); - a.value = "javascript://frozen"; - } - if ("contentWindow" in element) - element.replaceWith(element = element.cloneNode(true)); - - } - if (fa.length) - element._frozenAttributes = fa; - element._frozen = true; -} - -function mozilla_suppress_scripts(e) { - if (document.readyState === 'complete') { - removeEventListener('beforescriptexecute', blockExecute, true); - console.log('Script suppressor has detached.'); - return; - } - console.log("script event", e); - if (e.isTrusted && !e.target._hachette_payload) { - e.preventDefault(); - console.log('Suppressed script', e.target); - } -}; - -/* - * EXPORTS_START - * EXPORT mozilla_suppress_scripts - * EXPORT sanitize_attributes - * EXPORTS_END - */ diff --git a/content/main.js b/content/main.js index b2cc9ed..a183913 100644 --- a/content/main.js +++ b/content/main.js @@ -13,7 +13,6 @@ * IMPORT sign_data * IMPORT gen_nonce * IMPORT is_privileged_url - * IMPORT mozilla_suppress_scripts * IMPORT is_chrome * IMPORT is_mozilla * IMPORT start_activity_info_server @@ -132,10 +131,18 @@ function finish_waiting(waiting) function _wait_for_head(doc, detached_html, callback) { const waiting = {doc, detached_html, callback, observers: []}; - if (try_body_started(waiting)) - return; - waiting.observers = [make_body_start_observer(detached_html, waiting)]; + /* + * For XML and SVG documents, instead of waiting for `<head>', we wait + * for the entire document to finish loading. + */ + if (doc instanceof HTMLDocument) { + if (try_body_started(waiting)) + return; + + waiting.observers = [make_body_start_observer(detached_html, waiting)]; + } + waiting.loaded_cb = () => finish_waiting(waiting); doc.addEventListener("DOMContentLoaded", waiting.loaded_cb); } @@ -200,32 +207,72 @@ function desanitize_script(script, policy) delete script.hachette_blocked_type; } -function apply_hachette_csp_rules(doc, policy) +function apply_hachette_csp_rules(doc, head, policy) { const meta = doc.createElement("meta"); meta.setAttribute("http-equiv", "Content-Security-Policy"); meta.setAttribute("content", csp_rule(policy.nonce)); - doc.head.append(meta); + head.append(meta); /* CSP is already in effect, we can remove the <meta> now. */ meta.remove(); } +function sanitize_urls(element) +{ + for (const attribute of [...element.attributes]) { + if (/^(href|src|data)$/i.test(attribute.localName) && + /^data:([^,;]*ml|unknown-content-type)/i.test(attribute.value)) + block_attribute(element, attribute.localName); + } +} + +function start_data_urls_sanitizing(doc) +{ + doc.querySelectorAll("*[href], *[src], *[data]").forEach(sanitize_urls); + const mutation_handler = m => m.addedNodes.forEach(sanitize_urls); + const mo = new MutationObserver(ms => ms.forEach(mutation_handler)); + mo.observe(doc, {childList: true, subtree: true}); +} + +function apply_intrinsics_sanitizing(root_element) +{ + for (const subelem of root_element.querySelectorAll("*")) { + [...subelem.attributes] + .filter(a => /^on/i.test(a.localName)) + .filter(a => /^javascript:/i.test(a.value)) + .forEach(a => block_attribute(subelem, a.localName)); + } +} + async function sanitize_document(doc, policy) { /* + * Blocking of scripts that are in the DOM from the beginning. Needed for + * Mozilla, harmless on Chromium. + * Note that at least in SVG documents the `src' attr on `<script>'s seems + * to be ignored by Firefox, so we don't need to sanitize it. + */ + for (const script of document.getElementsByTagName("script")) { + const old_children = [...script.childNodes]; + script.innerHTML = ""; + setTimeout(() => old_children.forEach(c => script.append(c)), 0); + } + + /* * Ensure our CSP rules are employed from the beginning. This CSP injection * method is, when possible, going to be applied together with CSP rules * injected using webRequest. + * For non-HTML documents this is just a dummy operation of adding and + * removing `head'. */ - const has_own_head = doc.head; - if (!has_own_head) - doc.documentElement.prepend(doc.createElement("head")); + let added_head = doc.createElement("head"); + if (!doc.head) + doc.documentElement.prepend(added_head); - apply_hachette_csp_rules(doc, policy); + apply_hachette_csp_rules(doc, added_head, policy); - /* Probably not needed, but...: proceed with DOM in its initial state. */ - if (!has_own_head) - doc.head.remove(); + /* Proceed with DOM in its initial state. */ + added_head.remove(); /* * <html> node gets hijacked now, to be re-attached after <head> is loaded @@ -243,10 +290,15 @@ async function sanitize_document(doc, policy) for (const script of old_html.querySelectorAll("script")) sanitize_script(script, policy); + if (!(doc instanceof HTMLDocument)) + apply_intrinsics_sanitizing(old_html); + new_html.replaceWith(old_html); for (const script of old_html.querySelectorAll("script")) desanitize_script(script, policy); + + start_data_urls_sanitizing(doc); } if (!is_privileged_url(document.URL)) { |