[XSS] New UI to reveal and selectively remove permanent user choices.
This commit is contained in:
parent
1908b4b258
commit
2620d456b9
|
@ -145,6 +145,7 @@
|
||||||
sync: ns.sync,
|
sync: ns.sync,
|
||||||
unrestrictedTab: ns.unrestrictedTabs.has(tabId),
|
unrestrictedTab: ns.unrestrictedTabs.has(tabId),
|
||||||
tabId,
|
tabId,
|
||||||
|
xssBlockedInTab: XSS.getBlockedInTab(tabId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -109,8 +109,9 @@
|
||||||
<span id="xss-opt">
|
<span id="xss-opt">
|
||||||
<input type="checkbox" id="opt-xss"><label for="opt-xss" id="lbl-xss">__MSG_OptFilterXGet__</label>
|
<input type="checkbox" id="opt-xss"><label for="opt-xss" id="lbl-xss">__MSG_OptFilterXGet__</label>
|
||||||
<span id="xssFaq">(<a href="https://noscript.net/faq#xss" title="https://noscript.net/faq#xss">__MSG_XssFaq__</a>)</span>
|
<span id="xssFaq">(<a href="https://noscript.net/faq#xss" title="https://noscript.net/faq#xss">__MSG_XssFaq__</a>)</span>
|
||||||
<button id="btn-delete-xss-choices" disabled>__MSG_XSS_clearUserChoices__</button>
|
|
||||||
</span>
|
</span>
|
||||||
|
<div id="xssChoices">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="clearclick-options" class="opt-group">
|
<div id="clearclick-options" class="opt-group">
|
||||||
<input type="checkbox" id="opt-clearclick"><label for="opt-clearclick" id="lbl-clearclick">ClearClick</label>
|
<input type="checkbox" id="opt-clearclick"><label for="opt-clearclick" id="lbl-clearclick">ClearClick</label>
|
||||||
|
|
|
@ -108,16 +108,6 @@
|
||||||
url: a.href
|
url: a.href
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let button = document.querySelector("#btn-delete-xss-choices");
|
|
||||||
let choices = UI.xssUserChoices;
|
|
||||||
button.disabled = !choices || Object.keys(choices).length === 0;
|
|
||||||
button.onclick = () => {
|
|
||||||
UI.updateSettings({
|
|
||||||
xssUserChoices: {}
|
|
||||||
});
|
|
||||||
button.disabled = true
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opt("clearclick");
|
opt("clearclick");
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
><span class="tor">__MSG_OptOverrideTorBrowserPolicy__</span><span class="not-tor">__MSG_OptIncognitoPerm__</span></label>
|
><span class="tor">__MSG_OptOverrideTorBrowserPolicy__</span><span class="not-tor">__MSG_OptIncognitoPerm__</span></label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="xssChoices">
|
||||||
|
</div>
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
<div id="sites"></div>
|
<div id="sites"></div>
|
||||||
<div id="buttons">
|
<div id="buttons">
|
||||||
|
|
|
@ -519,4 +519,26 @@ legend {
|
||||||
.hilite-end .url {
|
.hilite-end .url {
|
||||||
transform: none;
|
transform: none;
|
||||||
transition: 1s transform;
|
transition: 1s transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#xssChoices {
|
||||||
|
padding: .5em;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xssChoices.populated {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xssChoices option {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xssChoices option.block {
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xssChoices option.allow {
|
||||||
|
color: #080;
|
||||||
|
}
|
||||||
|
|
57
src/ui/ui.js
57
src/ui/ui.js
|
@ -39,6 +39,7 @@ var UI = (() => {
|
||||||
UI.seen = m.seen;
|
UI.seen = m.seen;
|
||||||
UI.unrestrictedTab = m.unrestrictedTab;
|
UI.unrestrictedTab = m.unrestrictedTab;
|
||||||
UI.xssUserChoices = m.xssUserChoices;
|
UI.xssUserChoices = m.xssUserChoices;
|
||||||
|
UI.xssBlockedInTab = m.xssBlockedInTab;
|
||||||
UI.local = m.local;
|
UI.local = m.local;
|
||||||
UI.sync = m.sync;
|
UI.sync = m.sync;
|
||||||
UI.forceIncognito = UI.incognito && !UI.sync.overrideTorBrowserPolicy;
|
UI.forceIncognito = UI.incognito && !UI.sync.overrideTorBrowserPolicy;
|
||||||
|
@ -53,6 +54,7 @@ var UI = (() => {
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
if (UI.onSettings) UI.onSettings();
|
if (UI.onSettings) UI.onSettings();
|
||||||
|
if (UI.tabId === -1 || UI.xssBlockedInTab) UI.createXSSChoiceManager();
|
||||||
await HighContrast.init();
|
await HighContrast.init();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -128,6 +130,61 @@ var UI = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
createXSSChoiceManager(parent = "#xssChoices") {
|
||||||
|
let choicesUI = document.querySelector(parent);
|
||||||
|
if (!choicesUI) return;
|
||||||
|
choicesUI.classList.remove("populated");
|
||||||
|
let choices = Object.entries(UI.xssUserChoices);
|
||||||
|
let choiceKeys = UI.xssBlockedInTab;
|
||||||
|
if (choiceKeys) {
|
||||||
|
choices = choices.filter(([key,])=> choiceKeys.includes(key));
|
||||||
|
}
|
||||||
|
if (!choices || Object.keys(choices).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
choicesUI.classList.add("populated");
|
||||||
|
|
||||||
|
choices.sort((a, b) => {
|
||||||
|
let x = a.join("|"), y = b.join("|");
|
||||||
|
return x < y ? -1 : x > y ? 1 : 0;
|
||||||
|
});
|
||||||
|
let list = choicesUI.querySelector("select") || choicesUI.appendChild(document.createElement("select"));
|
||||||
|
list.size = Math.min(choices.length, 6);
|
||||||
|
list.multiple = true;
|
||||||
|
for (let o of list.options) {
|
||||||
|
list.remove(o);
|
||||||
|
}
|
||||||
|
for (let [originKey, choice] of choices) {
|
||||||
|
let [source, destOrigin] = originKey.split(">");
|
||||||
|
let opt = document.createElement("option");
|
||||||
|
opt.className = choice;
|
||||||
|
opt.value = originKey;
|
||||||
|
let block = choice === "block";
|
||||||
|
opt.defaultSelected = block;
|
||||||
|
opt.text = _(`XSS_optAlways${block ? "Block" : "Allow"}`, [source || "[...]", destOrigin]);
|
||||||
|
list.add(opt);
|
||||||
|
}
|
||||||
|
let button = choicesUI.querySelector("button");
|
||||||
|
if (!button) {
|
||||||
|
button = choicesUI.appendChild(document.createElement("button"));
|
||||||
|
button.textContent = _("XSS_clearUserChoices");
|
||||||
|
}
|
||||||
|
button.onclick = () => {
|
||||||
|
let xssUserChoices = UI.xssUserChoices;
|
||||||
|
for (let o of list.selectedOptions) {
|
||||||
|
delete xssUserChoices[o.value];
|
||||||
|
list.remove(o);
|
||||||
|
}
|
||||||
|
if (list.options.length === 0) {
|
||||||
|
choicesUI.classList.remove("populated");
|
||||||
|
}
|
||||||
|
UI.updateSettings({
|
||||||
|
xssUserChoices
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ include("InjectionChecker.js");
|
||||||
|
|
||||||
let Handlers = {
|
let Handlers = {
|
||||||
async check({xssReq, skip}) {
|
async check({xssReq, skip}) {
|
||||||
let {destUrl, unparsedRequest: request, debugging} = xssReq;
|
let {destUrl, request, debugging} = xssReq;
|
||||||
let {
|
let {
|
||||||
skipParams,
|
skipParams,
|
||||||
skipRx
|
skipRx
|
||||||
|
|
|
@ -6,19 +6,38 @@ var XSS = (() => {
|
||||||
|
|
||||||
let workersMap = new Map();
|
let workersMap = new Map();
|
||||||
let promptsMap = new Map();
|
let promptsMap = new Map();
|
||||||
|
let blockedTabs = new Map();
|
||||||
|
|
||||||
let requestIdCount = 0;
|
let requestIdCount = 0;
|
||||||
|
|
||||||
async function getUserResponse(xssReq) {
|
async function getUserResponse(xssReq) {
|
||||||
let {originKey} = xssReq;
|
let {originKey, request} = xssReq;
|
||||||
|
let {tabId, frameId} = request;
|
||||||
|
let {browserAction} = browser;
|
||||||
|
if (frameId === 0) {
|
||||||
|
if (blockedTabs.has(tabId)) {
|
||||||
|
blockedTabs.delete(tabId);
|
||||||
|
if ("setBadgeText" in browserAction) {
|
||||||
|
browserAction.setBadgeText({tabId, text: ""});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await promptsMap.get(originKey);
|
await promptsMap.get(originKey);
|
||||||
// promptsMap.delete(originKey);
|
|
||||||
switch (await XSS.getUserChoice(originKey)) {
|
switch (await XSS.getUserChoice(originKey)) {
|
||||||
case "allow":
|
case "allow":
|
||||||
return ALLOW;
|
return ALLOW;
|
||||||
case "block":
|
case "block":
|
||||||
log("Blocking request from %s to %s by previous XSS prompt user choice",
|
log("Blocking request from %s to %s by previous XSS prompt user choice",
|
||||||
xssReq.srcUrl, xssReq.destUrl);
|
xssReq.srcUrl, xssReq.destUrl);
|
||||||
|
|
||||||
|
if ("setBadgeText" in browserAction) {
|
||||||
|
browserAction.setBadgeText({tabId, text: "XSS"});
|
||||||
|
browserAction.setBadgeBackgroundColor({tabId, color: [0, 0, 128, 160]});
|
||||||
|
}
|
||||||
|
let keys = blockedTabs.get(tabId);
|
||||||
|
if (!keys) blockedTabs.set(tabId, keys = new Set());
|
||||||
|
keys.add(originKey);
|
||||||
return ABORT;
|
return ABORT;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -215,7 +234,7 @@ var XSS = (() => {
|
||||||
|
|
||||||
let isGet = method === "GET";
|
let isGet = method === "GET";
|
||||||
return {
|
return {
|
||||||
unparsedRequest: request,
|
request,
|
||||||
srcUrl,
|
srcUrl,
|
||||||
destUrl,
|
destUrl,
|
||||||
srcObj,
|
srcObj,
|
||||||
|
@ -247,6 +266,10 @@ var XSS = (() => {
|
||||||
return this._userChoices[originKey];
|
return this._userChoices[originKey];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getBlockedInTab(tabId) {
|
||||||
|
return blockedTabs.has(tabId) ? [...blockedTabs.get(tabId)] : null;
|
||||||
|
},
|
||||||
|
|
||||||
async maybe(xssReq) { // return reason or null if everything seems fine
|
async maybe(xssReq) { // return reason or null if everything seems fine
|
||||||
if (await this.Exceptions.shouldIgnore(xssReq)) {
|
if (await this.Exceptions.shouldIgnore(xssReq)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -254,7 +277,7 @@ var XSS = (() => {
|
||||||
|
|
||||||
let skip = this.Exceptions.partial(xssReq);
|
let skip = this.Exceptions.partial(xssReq);
|
||||||
let worker = new Worker(browser.runtime.getURL("/xss/InjectionCheckWorker.js"));
|
let worker = new Worker(browser.runtime.getURL("/xss/InjectionCheckWorker.js"));
|
||||||
let {requestId} = xssReq.unparsedRequest;
|
let {requestId} = xssReq.request;
|
||||||
workersMap.set(requestId, worker)
|
workersMap.set(requestId, worker)
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
worker.onmessage = e => {
|
worker.onmessage = e => {
|
||||||
|
@ -282,7 +305,7 @@ var XSS = (() => {
|
||||||
let onNavError = details => {
|
let onNavError = details => {
|
||||||
debug("Navigation error: %o", details);
|
debug("Navigation error: %o", details);
|
||||||
let {tabId, frameId, url} = details;
|
let {tabId, frameId, url} = details;
|
||||||
let r = xssReq.unparsedRequest;
|
let r = xssReq.request;
|
||||||
if (tabId === r.tabId && frameId === r.frameId) {
|
if (tabId === r.tabId && frameId === r.frameId) {
|
||||||
cleanup();
|
cleanup();
|
||||||
reject(new Error("Timing: request interrupted while being filtered, no need to go on."));
|
reject(new Error("Timing: request interrupted while being filtered, no need to go on."));
|
||||||
|
|
Loading…
Reference in New Issue