diff --git a/src/bg/main.js b/src/bg/main.js index de01665..5737f9c 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -315,6 +315,16 @@ include("/test/run.js"); }, + async testIC(callbackOrUrl) { + await include("xss/InjectionChecker.js"); + let IC = await XSS.InjectionChecker; + let ic = new IC(); + ic.logEnabled = true; + return (typeof callbackOrUrl === "function") + ? await callbackOrUrl(ic) + : ic.checkUrl(callbackOrUrl); + }, + async savePolicy() { if (this.policy) { await Storage.set("sync", { diff --git a/src/content/staticNS.js b/src/content/staticNS.js index c5211f5..d85e9bc 100644 --- a/src/content/staticNS.js +++ b/src/content/staticNS.js @@ -86,14 +86,18 @@ debug("Fetching policy for actual URL %s (was %s)", url, document.URL); } - debug(`Synchronously fetching policy for ${url}.`); if (this.syncFetchPolicy) { // extra hops to ensure that scripts don't run when CSP has not been set through HTTP headers this.syncFetchPolicy(); } else { - this.setup( - browser.runtime.sendSyncMessage({id: "fetchPolicy", url}) - ); + let msg = {id: "fetchPolicy", url}; + if (document.readyState === "complete") { + // no point fetching synchronously, since the document is already loaded (hot extension update?) + (async () => this.setup(await browser.runtime.sendMessage(msg)))(); + } else { + debug(`Synchronously fetching policy for ${url}.`); + this.setup(browser.runtime.sendSyncMessage(msg)); + } } }, diff --git a/src/manifest.json b/src/manifest.json index d90d2e1..d435141 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -8,7 +8,7 @@ "strict_min_version": "59.0" } }, - "version": "11.2.16rc3", + "version": "11.2.19", "description": "__MSG_Description__", "incognito": "spanning", @@ -51,7 +51,6 @@ "/nscl/common/Permissions.js", "/nscl/common/Policy.js", "/nscl/common/locale.js", - "/nscl/common/SyntaxChecker.js", "/nscl/common/Storage.js", "/nscl/common/include.js", "/nscl/service/DocStartInjection.js", diff --git a/src/nscl b/src/nscl index f04ec6a..fa49ecb 160000 --- a/src/nscl +++ b/src/nscl @@ -1 +1 @@ -Subproject commit f04ec6afe193f59ce540f5de9e0d184bf7d8a231 +Subproject commit fa49ecb52140aa80db30a7fa834f6358acc94a13 diff --git a/src/test/XSS_test.js b/src/test/XSS_test.js index e2da8d7..e909ff4 100644 --- a/src/test/XSS_test.js +++ b/src/test/XSS_test.js @@ -21,7 +21,7 @@ if (UA.isMozilla) { let y = async (url, originUrl = '') => await XSS.test({originUrl, url, method: "GET"}); let n = async (...args) => !await y(...args); - Promise.all([ + let xssTest = Promise.all([ () => y("https://noscript.net/ n("https://noscript.net/ y("https://vulnerabledoma.in/char_test?body=%80%3Cscript%3Ealert(1)%3C/script%3E"), @@ -32,25 +32,25 @@ if (UA.isMozilla) { () => y("https://vulnerabledoma.in/xss_link?url=javascript%26colo%00n%3Balert%u00281%29"), () => y("https://vulnerabledoma.in/xss_link?url=javascript:\\u{%0A6e}ame"), ].map(t => Test.run(t)) - ).then(() => Test.report()); + ); let invalidCharsTest = async () => { await include("xss/InjectionChecker.js"); let IC = await XSS.InjectionChecker; let rx = new IC().invalidCharsRx; - + console.log("Testing invalidCharsRx", rx); let x = n => '\\u' + ("0000" + n.toString(16)).slice(-4); function check(ch) { - eval(`{let _${ch}_}`); + Function(`let _${ch}_`); } let cur = 0x7e; let fail = false; - while (cur++ < 0xffff) { + while (cur++ < 0xffff && !fail) { let ch = String.fromCharCode(cur); try { check(ch); - if (tx.test(ch)) { + if (rx.test(ch)) { console.error(x(cur) + " should not test invalid!"); fail = true; } @@ -64,6 +64,10 @@ if (UA.isMozilla) { } return !fail; }; - - Test.run(invalidCharsTest, "InjectionChecker.invalidCharsRx").then(Test.report()); + (async () => { + await xssTest; + Test.report(); + await Test.run(invalidCharsTest, "InjectionChecker.invalidCharsRx"); + Test.report(); + })(); } diff --git a/src/xss/InjectionChecker.js b/src/xss/InjectionChecker.js index 0f3a250..1463443 100644 --- a/src/xss/InjectionChecker.js +++ b/src/xss/InjectionChecker.js @@ -526,7 +526,15 @@ XSS.InjectionChecker = (async () => { }, get invalidCharsRx() { - let value = new RegExp("^[^\"'`/<>]*[" + this._createInvalidRanges() + "]"); + let preamble = "^[^\"'`/<>]*"; + let value; + try { + // see https://mathiasbynens.be/notes/javascript-identifiers-es6#acceptable-unicode-symbols + value = new RegExp(preamble + "[^$_\\p{ID_Start}\\p{ID_Continue}\\u200c\\u200d\\u2028\\u2029]", "u"); + } catch (e) { + // Unicode entities are not supported in Gecko <= 77 + value = new RegExp(preamble + `[${this._createInvalidRanges()}]`, "u"); + } Object.defineProperty(Object.getPrototypeOf(this), 'invalidCharsRx', {value}); return value; }, @@ -886,7 +894,7 @@ XSS.InjectionChecker = (async () => { l = l.replace(/[^=]*=\s*/i, '').replace(/[\u0000-\u001f]/g, ''); l = /^["']/.test(l) ? l.replace(/^(['"])([^]*?)\1[^]*/g, '$2') : l.replace(/[\s>][^]*/, ''); - if (/^(?:javascript|data):|\[[^]+\]/i.test(l) || /[<'"(]/.test(unescape(l)) && await this.checkUrl(l)) return true; + if (/^(?:javascript|data):/i.test(l) || /[<'"([]/.test(unescape(l)) && await this.checkUrl(l)) return true; } } return this._rxCheck("HTML", s) || this._rxCheck("Globals", s); diff --git a/src/xss/XSS.js b/src/xss/XSS.js index 8e68ac8..59e0c90 100644 --- a/src/xss/XSS.js +++ b/src/xss/XSS.js @@ -117,10 +117,14 @@ var XSS = (() => { if (reasons.protectName) { await include("/nscl/service/ContentScriptOnce.js"); - await ContentScriptOnce.execute(request, { - js: [{file: "/xss/sanitizeName.js"}], - }); - if (!block) return ALLOW; + try { + await ContentScriptOnce.execute(request, { + js: [{file: "/xss/sanitizeName.js"}], + }); + if (!block) return ALLOW; + } catch (e) { + error(e, "Sanitizing name in request", request.url); + } } if (reasons.urlInjection) data.push(`(URL) ${unescapedDest}`); if (reasons.postInjection) data.push(`(POST) ${reasons.postInjection}`);