From 54df9f8a30e442c26bcb40d1047190d04a821199 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 28 Feb 2019 10:02:19 +0100 Subject: [PATCH 1/4] WIP fix 1 --- application/palemoon/base/content/browser.js | 5 ++ .../palemoon/base/content/tabbrowser.xml | 5 +- .../palemoon/base/content/urlbarBindings.xml | 4 ++ .../passwordmgr/nsLoginManagerPrompter.js | 48 +++++++++++-------- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/application/palemoon/base/content/browser.js b/application/palemoon/base/content/browser.js index 3feeef9b6c..4e753d4228 100644 --- a/application/palemoon/base/content/browser.js +++ b/application/palemoon/base/content/browser.js @@ -2661,6 +2661,11 @@ function getWebNavigation() } function BrowserReloadWithFlags(reloadFlags) { + + // Reset DOS mitigation for auth prompts when user initiates a reload. + let browser = gBrowser.selectedBrowser; + delete browser.authPromptCounter; + /* First, we'll try to use the session history object to reload so * that framesets are handled properly. If we're in a special * window (such as view-source) that has no session history, fall diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index 988cae55c4..c3b4872dbe 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -2458,7 +2458,10 @@ diff --git a/application/palemoon/base/content/urlbarBindings.xml b/application/palemoon/base/content/urlbarBindings.xml index d188e66585..d2d9cc7204 100644 --- a/application/palemoon/base/content/urlbarBindings.xml +++ b/application/palemoon/base/content/urlbarBindings.xml @@ -302,6 +302,10 @@ // but don't let that interfere with the loading of the url. Cu.reportError(ex); } + + // Reset DOS mitigations for the basic auth prompt. + let browser = gBrowser.selectedBrowser; + delete browser.authPromptCounter; function loadCurrent() { let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 720e80446b..35315110c2 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -97,17 +97,25 @@ LoginManagerPromptFactory.prototype = { return; } - // Allow only a limited number of authentication dialogs when they are all - // canceled by the user. - var cancelationCounter = (prompter._browser && prompter._browser.canceledAuthenticationPromptCounter) || { count: 0, id: 0 }; - if (prompt.channel) { - var httpChannel = prompt.channel.QueryInterface(Ci.nsIHttpChannel); - if (httpChannel) { - var windowId = httpChannel.topLevelContentWindowId; - if (windowId != cancelationCounter.id) { - // window has been reloaded or navigated, reset the counter - cancelationCounter = { count: 0, id: windowId }; - } + // Set up a counter for ensuring that the basic auth prompt can not + // be abused for DOS-style attacks. With this counter, each eTLD+1 + // per browser will get a limited number of times a user can + // cancel the prompt until we stop showing it. + let browser = prompter._browser; + let baseDomain = null; + if (browser) { + try { + baseDomain = Services.eTLD.getBaseDomainFromHost(hostname); + } catch (e) { + baseDomain = hostname; + } + + if (!browser.authPromptCounter) { + browser.authPromptCounter = {}; + } + + if (!browser.authPromptCounter[baseDomain]) { + browser.authPromptCounter[baseDomain] = 0; } } @@ -137,13 +145,14 @@ LoginManagerPromptFactory.prototype = { prompt.inProgress = false; self._asyncPromptInProgress = false; - if (ok) { - cancelationCounter.count = 0; - } else { - cancelationCounter.count++; - } - if (prompter._browser) { - prompter._browser.canceledAuthenticationPromptCounter = cancelationCounter; + if (browser) { + // Reset the counter state if the user replied to a prompt and actually + // tried to login (vs. simply clicking any button to get out). + if (ok && (prompt.authInfo.username || prompt.authInfo.password)) { + browser.authPromptCounter[baseDomain] = 0; + } else { + browser.authPromptCounter[baseDomain] += 1; + } } } @@ -168,8 +177,9 @@ LoginManagerPromptFactory.prototype = { var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit"); + let cancelationCounter = browser.authPromptCounter[baseDomain]; this.log("cancelationCounter =", cancelationCounter); - if (cancelDialogLimit && cancelationCounter.count >= cancelDialogLimit) { + if (cancelDialogLimit && cancelationCounter >= cancelDialogLimit) { this.log("Blocking auth dialog, due to exceeding dialog bloat limit"); delete this._asyncPrompts[hashKey]; From c283102a46493c4629227725d558f1e0f8347489 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 28 Feb 2019 13:58:23 +0100 Subject: [PATCH 2/4] Move default-enable pref to application. --- application/palemoon/app/profile/palemoon.js | 8 ++++++++ modules/libpref/init/all.js | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/application/palemoon/app/profile/palemoon.js b/application/palemoon/app/profile/palemoon.js index 43f020f9a1..3df5d71944 100644 --- a/application/palemoon/app/profile/palemoon.js +++ b/application/palemoon/app/profile/palemoon.js @@ -1160,6 +1160,14 @@ pref("toolkit.pageThumbs.minHeight", 180); pref("ui.key.menuAccessKeyFocuses", true); #endif +// When a user cancels this number of authentication dialogs coming from +// a single web page (eTLD+1) in a row, all following authentication dialogs +// will be blocked (automatically canceled) for that page. +// This counter is per-tab and per-domain to minimize false positives. +// The counter resets when the page is reloaded from the UI +// (content-reloads do NOT clear this to mitigate reloading tricks). +pref("prompts.authentication_dialog_abuse_limit", 3); + // ****************** s4e prefs ****************** pref("status4evar.addonbar.borderStyle", false); pref("status4evar.addonbar.closeButton", false); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 7156b2a0af..91a9a1bfca 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -5459,8 +5459,9 @@ pref("dom.storageManager.enabled", true); pref("dom.storageManager.enabled", false); #endif -// When a user cancels this number of authentication dialogs coming from -// a single web page in a row, all following authentication dialogs will -// be blocked (automatically canceled) for that page. The counter resets -// when the page is reloaded. To turn this feature off, just set the limit to 0. -pref("prompts.authentication_dialog_abuse_limit", 3); +// DoS protection for HTTP Auth prompt spawning. +// -1 = completely disable HTTP Auth prompting. (careful!) +// 0 = disable this DoS protection +// >0 = suppress further prompts after the user has canceled the dialog n times +// See application preferences for appropriate defaults. +pref("prompts.authentication_dialog_abuse_limit", 0); From 7b71e22beeda47aa5497a12f81fc73c113df2813 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 28 Feb 2019 14:16:09 +0100 Subject: [PATCH 3/4] Update Basilisk FE code with HTTP Auth DoS protection. --- application/basilisk/app/profile/basilisk.js | 8 ++++++++ application/basilisk/base/content/browser.js | 5 +++++ application/basilisk/base/content/tabbrowser.xml | 5 ++++- application/basilisk/base/content/urlbarBindings.xml | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/application/basilisk/app/profile/basilisk.js b/application/basilisk/app/profile/basilisk.js index 79d767851d..24f1c582cc 100644 --- a/application/basilisk/app/profile/basilisk.js +++ b/application/basilisk/app/profile/basilisk.js @@ -1438,3 +1438,11 @@ pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false); // controlling validation are located in /services/sync/services-sync.js pref("services.sync.validation.enabled", true); #endif + +// When a user cancels this number of authentication dialogs coming from +// a single web page (eTLD+1) in a row, all following authentication dialogs +// will be blocked (automatically canceled) for that page. +// This counter is per-tab and per-domain to minimize false positives. +// The counter resets when the page is reloaded from the UI +// (content-reloads do NOT clear this to mitigate reloading tricks). +pref("prompts.authentication_dialog_abuse_limit", 3); diff --git a/application/basilisk/base/content/browser.js b/application/basilisk/base/content/browser.js index 893934166c..af2545fcd5 100644 --- a/application/basilisk/base/content/browser.js +++ b/application/basilisk/base/content/browser.js @@ -3099,6 +3099,11 @@ function getWebNavigation() } function BrowserReloadWithFlags(reloadFlags) { + + // Reset DOS mitigation for auth prompts when user initiates a reload. + let browser = gBrowser.selectedBrowser; + delete browser.authPromptCounter; + let url = gBrowser.currentURI.spec; if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) { // If the remoteness has changed, the new browser doesn't have any diff --git a/application/basilisk/base/content/tabbrowser.xml b/application/basilisk/base/content/tabbrowser.xml index 0408350018..700f6426c1 100644 --- a/application/basilisk/base/content/tabbrowser.xml +++ b/application/basilisk/base/content/tabbrowser.xml @@ -3007,7 +3007,10 @@ diff --git a/application/basilisk/base/content/urlbarBindings.xml b/application/basilisk/base/content/urlbarBindings.xml index eb31505812..4c010bffb4 100644 --- a/application/basilisk/base/content/urlbarBindings.xml +++ b/application/basilisk/base/content/urlbarBindings.xml @@ -554,6 +554,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. Cu.reportError(ex); } + // Reset DOS mitigations for the basic auth prompt. + delete browser.authPromptCounter; + let params = { postData, allowThirdPartyFixup: true, From 1e6b075d1d239107edcbf720f5b7d0e149183190 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Fri, 1 Mar 2019 14:01:09 +0100 Subject: [PATCH 4/4] Make the Auth prompt DOS protection a browser-element opt-in feature. --- application/basilisk/base/content/browser.xul | 3 +- .../basilisk/base/content/tabbrowser.xml | 6 +++- application/palemoon/base/content/browser.xul | 3 +- .../palemoon/base/content/tabbrowser.xml | 6 +++- .../passwordmgr/nsLoginManagerPrompter.js | 30 +++++++++++++------ toolkit/content/widgets/browser.xml | 4 +++ 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/application/basilisk/base/content/browser.xul b/application/basilisk/base/content/browser.xul index 6b7a10dc7f..c732c3263a 100644 --- a/application/basilisk/base/content/browser.xul +++ b/application/basilisk/base/content/browser.xul @@ -999,7 +999,8 @@ contentcontextmenu="contentAreaContextMenu" autocompletepopup="PopupAutoComplete" selectmenulist="ContentSelectDropdown" - datetimepicker="DateTimePickerPanel"/> + datetimepicker="DateTimePickerPanel" + authdosprotected="true" /> diff --git a/application/palemoon/base/content/tabbrowser.xml b/application/palemoon/base/content/tabbrowser.xml index c3b4872dbe..cbe029af03 100644 --- a/application/palemoon/base/content/tabbrowser.xml +++ b/application/palemoon/base/content/tabbrowser.xml @@ -30,7 +30,7 @@ + xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,datetimepicker,authdosprotected"/> @@ -1588,6 +1588,10 @@ if (this.hasAttribute("datetimepicker")) { b.setAttribute("datetimepicker", this.getAttribute("datetimepicker")); } + + if (this.hasAttribute("authdosprotected")) { + b.setAttribute("authdosprotected", this.getAttribute("authdosprotected")); + } // Create the browserStack container var stack = document.createElementNS(NS_XUL, "stack"); diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 35315110c2..c4be39e318 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -103,7 +103,7 @@ LoginManagerPromptFactory.prototype = { // cancel the prompt until we stop showing it. let browser = prompter._browser; let baseDomain = null; - if (browser) { + if (browser && browser.isAuthDOSProtected) { try { baseDomain = Services.eTLD.getBaseDomainFromHost(hostname); } catch (e) { @@ -145,7 +145,7 @@ LoginManagerPromptFactory.prototype = { prompt.inProgress = false; self._asyncPromptInProgress = false; - if (browser) { + if (browser && browser.isAuthDOSProtected) { // Reset the counter state if the user replied to a prompt and actually // tried to login (vs. simply clicking any button to get out). if (ok && (prompt.authInfo.username || prompt.authInfo.password)) { @@ -177,15 +177,27 @@ LoginManagerPromptFactory.prototype = { var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit"); - let cancelationCounter = browser.authPromptCounter[baseDomain]; - this.log("cancelationCounter =", cancelationCounter); - if (cancelDialogLimit && cancelationCounter >= cancelDialogLimit) { - this.log("Blocking auth dialog, due to exceeding dialog bloat limit"); - delete this._asyncPrompts[hashKey]; + // Block the auth prompt if: + // - There is an attached browser element + // - The browser element has opted-in to DOS protection + // - The dialog cancellation limit is not 0 (= feature disabled) + // - The amount of cancellations >= the set abuse limit + if (browser && browser.isAuthDOSProtected) { + let cancelationCounter = browser.authPromptCounter[baseDomain]; + this.log("cancelationCounter =", cancelationCounter); - // just make the runnable cancel all consumers - runnable.cancel = true; + if (cancelDialogLimit && cancelationCounter >= cancelDialogLimit) { + this.log("Blocking auth dialog, due to exceeding dialog bloat limit"); + delete this._asyncPrompts[hashKey]; + + // just make the runnable cancel all consumers + runnable.cancel = true; + } else { + this._asyncPromptInProgress = true; + prompt.inProgress = true; + } } else { + // No DOS protection: prompt this._asyncPromptInProgress = true; prompt.inProgress = true; } diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index a30ff1c43c..5a0a99bf86 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -899,6 +899,10 @@ null + + null