From d1dd278a81444e2203945fc213a4b69ed1ee49a7 Mon Sep 17 00:00:00 2001 From: hackademix Date: Thu, 14 Mar 2019 01:57:58 +0100 Subject: Selective handling of Tor Browser options and work-around for https://bugzilla.mozilla.org/show_bug.cgi?id=1532530 --- src/_locales/en/messages.json | 12 +++++++ src/bg/Defaults.js | 5 ++- src/bg/RequestGuard.js | 2 +- src/bg/Settings.js | 75 ++++++++++++++++++++++++++++++++++++------- src/bg/main.js | 4 +++ src/ui/options.css | 10 ++++++ src/ui/options.html | 22 +++++++++++-- src/ui/options.js | 9 ++++-- src/ui/ui.js | 3 ++ src/xss/XSS.js | 18 +++++++++-- 10 files changed, 138 insertions(+), 22 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index cd0fb52..13ab4c3 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -267,6 +267,18 @@ "OptFilterXPost": { "message": "Turn cross-site POST requests into data-less GET requests" }, + "OptScanXUpload": { + "message": "Scan uploads for potential cross-site attacks" + }, + "OptBlockUnscannedXPost": { + "message": "Ask confirmation for cross-site POST requests which could not be scanned" + }, + "UnscannedXPost": { + "message": "This cross-site request could not be scanned for XSS.\nIt might be innocuous, but NoScript cannot tell for sure. Allow only if you trust both sites." + }, + "OptOverrideTorBrowserPolicy": { + "message": "Override Tor Browser's Security Level preset" + }, "Options": { "message": "Options…" }, diff --git a/src/bg/Defaults.js b/src/bg/Defaults.js index ef2311f..892ee40 100644 --- a/src/bg/Defaults.js +++ b/src/bg/Defaults.js @@ -12,7 +12,10 @@ var Defaults = { sync: { "global": false, "xss": true, - "clearclick": true + "xssScanRequestBody": true, + "xssBlockUnscannedPOST": false, + "overrideTorBrowserPolicy": false, // note: Settings.update() on reset will flip this to true + "clearclick": true, } }; let defaultsClone = JSON.parse(JSON.stringify(defaults)); diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js index a174eba..2f590dc 100644 --- a/src/bg/RequestGuard.js +++ b/src/bg/RequestGuard.js @@ -379,7 +379,7 @@ var RequestGuard = (() => { if (pending) { pending.scriptBlocked = scriptBlocked; if (!(pending.headersProcessed && - (scriptBlocked || !ns.isEnforced(tabId) || ns.policy.can(url, "script", request.documentURL)) + (scriptBlocked || !ns.requestCan(request, "script")) )) { debug("[WARNING] onHeadersReceived %s %o", frameId, tabId, pending.headersProcessed ? "has been overridden on": "could not process", diff --git a/src/bg/Settings.js b/src/bg/Settings.js index 0a911ee..3efc4ad 100644 --- a/src/bg/Settings.js +++ b/src/bg/Settings.js @@ -81,8 +81,56 @@ var Settings = { tabId, unrestrictedTab, reloadAffected, + isTorBrowser, } = settings; - if (xssUserChoices) await XSS.saveUserChoices(xssUserChoices); + + let oldDebug = ns.local.debug; + + let reloadOptionsUI = false; + + if (isTorBrowser) { + // Tor Browser-specific settings + ns.defaults.local.isTorBrowser = true; // prevents reset from forgetting + if (!this.gotTorBrowserInit) { + // First initialization message from the Tor Browser + this.gotTorBrowserInit = true; + if (ns.sync.overrideTorBrowserPolicy) { + // If the user chose to override Tor Browser's policy we skip + // copying the Security Level preset on startup (only). + // Manually changing the security level works as usual. + ns.local.isTorBrowser = true; + await ns.save(ns.local); + return; + } + } else { + reloadOptionsUI = true; + } + if (!settings.local) settings.local = {}; + settings.local.isTorBrowser = true; + if (!settings.sync) settings.sync = {}; + settings.sync.xssScanRequestBody = false; + settings.sync.xssBlockUnscannedPOST = true; + } + + if (settings.sync === null) { + // overriden defaults when user manually resets options + + // we want the reset options to stick (otherwise it gets very confusing) + ns.defaults.sync.overrideTorBrowserPolicy = true; + reloadOptionsUI = true; + } + + await Promise.all(["local", "sync"].map( + async storage => (settings[storage] || // changed or... + settings[storage] === null // ... needs reset to default + ) && await ns.save(settings[storage] + ? Object.assign(ns[storage], settings[storage]) : ns[storage] = ns.defaults[storage]) + )); + if (ns.local.debug !== oldDebug) { + await include("/lib/log.js"); + if (oldDebug) debug = () => {}; + } + if (policy) { ns.policy = new Policy(policy); await ns.savePolicy(); @@ -95,22 +143,15 @@ var Settings = { browser.tabs.reload(tabId); } - let oldDebug = ns.local.debug; - await Promise.all(["local", "sync"].map( - storage => (settings[storage] || // changed or... - settings[storage] === null // ... needs reset to default - ) && ns.save( - ns[storage] = settings[storage] || ns.defaults[storage]) - )); - if (ns.local.debug !== oldDebug) { - await include("/lib/log.js"); - if (oldDebug) debug = () => {}; - } + if (xssUserChoices) await XSS.saveUserChoices(xssUserChoices); + if (ns.sync.xss) { XSS.start(); } else { XSS.stop(); } + + if (reloadOptionsUI) await this.reloadOptionsUI(); }, export() { @@ -125,5 +166,15 @@ var Settings = { async enforceTabRestrictions(tabId, unrestricted = ns.unrestrictedTabs.has(tabId)) { await ChildPolicies.storeTabInfo(tabId, unrestricted && {unrestricted: true}); return unrestricted; + }, + + async reloadOptionsUI() { + try { + for (let t of await browser.tabs.query({url: browser.runtime.getManifest().options_ui.page })) { + browser.tabs.reload(t.id); + }; + } catch (e) { + error(e); + } } } diff --git a/src/bg/main.js b/src/bg/main.js index 18abe70..7bd72a6 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -180,6 +180,10 @@ return this.policy.enforced && (tabId === -1 || !this.unrestrictedTabs.has(tabId)); }, + requestCan(request, capability) { + return !this.isEnforced(request.tabId) || this.policy.can(request.url, "script", request.documentURL); + }, + start() { if (this.running) return; this.running = true; diff --git a/src/ui/options.css b/src/ui/options.css index f7db24b..6e6eec7 100644 --- a/src/ui/options.css +++ b/src/ui/options.css @@ -136,9 +136,19 @@ input[type="file"] { .opt-group { padding: 0.5em 0; } + #xssFaq { padding: 0.5em 1em; } + +#tb-options { + display: none; +} + +.tor #tb-options { + display: initial; +} + #clearclick-options { display: none; } diff --git a/src/ui/options.html b/src/ui/options.html index 6e2ad0e..a80dece 100644 --- a/src/ui/options.html +++ b/src/ui/options.html @@ -99,17 +99,33 @@

-
- +
+ - (__MSG_XssFaq__) + (__MSG_XssFaq__) +
+ + + + + + + +
+
+ + + + +
+
diff --git a/src/ui/options.js b/src/ui/options.js index 31cf5c3..7a9ca2b 100644 --- a/src/ui/options.js +++ b/src/ui/options.js @@ -8,9 +8,9 @@ let version = browser.runtime.getManifest().version; document.querySelector("#version").textContent = _("Version", version); // simple general options - + let opt = UI.wireOption; - + opt("global", o => { if (o) { policy.enforced = !o.checked; @@ -33,6 +33,11 @@ }); opt("xss"); + opt("xssScanRequestBody"); + opt("xssBlockUnscannedPOST"); + + opt("overrideTorBrowserPolicy"); + { let button = document.querySelector("#btn-reset"); button.onclick = async () => { diff --git a/src/ui/ui.js b/src/ui/ui.js index 205d2a9..46d9d7f 100644 --- a/src/ui/ui.js +++ b/src/ui/ui.js @@ -41,6 +41,9 @@ var UI = (() => { if (UI.local && !UI.local.debug) { debug = () => {}; // be quiet! } + if (UI.local) { + document.documentElement.classList.toggle("tor", !!UI.local.isTorBrowser); + } resolve(); if (UI.onSettings) UI.onSettings(); await HighContrast.init(); diff --git a/src/xss/XSS.js b/src/xss/XSS.js index f95ea04..b7bffce 100644 --- a/src/xss/XSS.js +++ b/src/xss/XSS.js @@ -114,6 +114,13 @@ var XSS = (() => { return { async start() { let {onBeforeRequest} = browser.webRequest; + let {xssScanRequestBody} = ns.sync; + if (xssScanRequestBody !== this.xssScanRequestBody) { + this.stop(); + this.xssScanRequestBody = xssScanRequestBody; + } + this.xssBlockUnscannedPOST = ns.sync.xssBlockUnscannedPOST; + if (onBeforeRequest.hasListener(requestListener)) return; await include("/legacy/Legacy.js"); @@ -135,7 +142,9 @@ var XSS = (() => { onBeforeRequest.addListener(requestListener, { urls: ["*://*/*"], types: ["main_frame", "sub_frame", "object"] - }, ["blocking", "requestBody"]); + }, + // work-around for https://bugzilla.mozilla.org/show_bug.cgi?id=1532530 + xssScanRequestBody ? ["blocking", "requestBody"] : ["blocking"]); }, stop() { @@ -233,8 +242,11 @@ var XSS = (() => { ic.reset(); let postInjection = xssReq.isPost && - request.requestBody && request.requestBody.formData && - ic.checkPost(request.requestBody.formData, skipParams); + (XSS.xssScanRequestBody ? + request.requestBody && request.requestBody.formData && + ic.checkPost(request.requestBody.formData, skipParams) + : XSS.xssBlockUnscannedPOST && ns.requestCan(request, "script") && _("UnscannedXPost") + ); let protectName = ic.nameAssignment; let urlInjection = ic.checkUrl(destUrl, skipRx); -- cgit v1.2.3