#547: block modal HTTP auth DOS M1312243 M377496 + glue code

This commit is contained in:
Cameron Kaiser 2019-03-26 22:23:40 -07:00
parent 9465ea36d8
commit d60d9721fd
15 changed files with 207 additions and 23 deletions

View File

@ -3259,6 +3259,10 @@ function BrowserReloadWithFlags(reloadFlags) {
return; return;
} }
// Reset temporary permissions on the current tab. This is done here
// because we only want to reset permissions on user reload.
delete gBrowser.selectedBrowser.canceledAuthenticationPromptCounter;
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils); .getInterface(Ci.nsIDOMWindowUtils);

View File

@ -2679,6 +2679,7 @@ let remote = false;
<parameter name="aTab"/> <parameter name="aTab"/>
<body> <body>
<![CDATA[ <![CDATA[
delete this.getBrowserForTab(aTab).canceledAuthenticationPromptCounter;
this.getBrowserForTab(aTab).reload(); this.getBrowserForTab(aTab).reload();
]]> ]]>
</body> </body>

View File

@ -417,6 +417,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// occurs in a new tab, we want focus to be restored to the content // occurs in a new tab, we want focus to be restored to the content
// area when the current tab is re-selected. // area when the current tab is re-selected.
gBrowser.selectedBrowser.focus(); gBrowser.selectedBrowser.focus();
delete gBrowser.selectedBrowser.canceledAuthenticationPromptCounter;
let isMouseEvent = aTriggeringEvent instanceof MouseEvent; let isMouseEvent = aTriggeringEvent instanceof MouseEvent;

View File

@ -84,6 +84,7 @@
#include "nsWhitespaceTokenizer.h" #include "nsWhitespaceTokenizer.h"
#include "nsICookieService.h" #include "nsICookieService.h"
#include "nsIConsoleReportCollector.h" #include "nsIConsoleReportCollector.h"
#include "nsILoginManagerPrompter.h"
// we want to explore making the document own the load group // we want to explore making the document own the load group
// so we can associate the document URI with the load group. // so we can associate the document URI with the load group.
@ -13169,6 +13170,35 @@ nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
// Get the an auth prompter for our window so that the parenting // Get the an auth prompter for our window so that the parenting
// of the dialogs works as it should when using tabs. // of the dialogs works as it should when using tabs.
// Since we don't go through E10S, we need to set the browser element
// manually (a la dom/ipc/TabParent) so that we can check elements
// that may be set on it, such as HTTP Auth DOS (TenFourFox issue 547).
NS_ASSERTION(mScriptGlobal, "We don't have a script global");
if (MOZ_LIKELY(mScriptGlobal)) {
nsCOMPtr<Element> frameElement = mScriptGlobal->GetFrameElementInternal();
if (MOZ_LIKELY(frameElement)) {
nsCOMPtr<nsIContent> frame = do_QueryInterface(frameElement);
if (MOZ_LIKELY(frame)) {
nsCOMPtr<nsISupports> prompt;
nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(frame->OwnerDoc()->GetWindow());
if (MOZ_LIKELY(window) &&
NS_SUCCEEDED(wwatch->GetPrompt(window, aIID,
getter_AddRefs(prompt))) &&
MOZ_LIKELY(prompt)) {
nsCOMPtr<nsIDOMElement> browser = do_QueryInterface(frameElement);
if (MOZ_LIKELY(browser)) {
nsCOMPtr<nsILoginManagerPrompter> prompter = do_QueryInterface(prompt);
if (MOZ_LIKELY(prompter))
prompter->SetE10sData(browser, nullptr);
}
*aResult = prompt.forget().take();
return NS_OK;
}
}
}
NS_WARNING("Unable to connect browser to auth prompt, falling back");
}
return wwatch->GetPrompt(mScriptGlobal, aIID, return wwatch->GetPrompt(mScriptGlobal, aIID,
reinterpret_cast<void**>(aResult)); reinterpret_cast<void**>(aResult));

View File

@ -5151,6 +5151,12 @@ pref("toolkit.pageThumbs.screenSizeDivisor", 7);
pref("toolkit.pageThumbs.minWidth", 0); pref("toolkit.pageThumbs.minWidth", 0);
pref("toolkit.pageThumbs.minHeight", 0); pref("toolkit.pageThumbs.minHeight", 0);
// 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);
pref("tenfourfox.adblock.enabled", false); pref("tenfourfox.adblock.enabled", false);
pref("tenfourfox.adblock.logging.enabled", false); pref("tenfourfox.adblock.logging.enabled", false);
pref("tenfourfox.dom.forms.date", true); pref("tenfourfox.dom.forms.date", true);

View File

@ -117,6 +117,7 @@ struct HttpChannelOpenArgs
OptionalCorsPreflightArgs preflightArgs; OptionalCorsPreflightArgs preflightArgs;
uint32_t initialRwin; uint32_t initialRwin;
bool suspendAfterSynthesizeResponse; bool suspendAfterSynthesizeResponse;
uint64_t contentWindowId;
}; };
struct HttpChannelConnectArgs struct HttpChannelConnectArgs

View File

@ -48,8 +48,10 @@
#include "LoadInfo.h" #include "LoadInfo.h"
#include "nsIXULRuntime.h" #include "nsIXULRuntime.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsIDOMWindowUtils.h"
#include <algorithm> #include <algorithm>
#include "HttpBaseChannel.h"
namespace mozilla { namespace mozilla {
namespace net { namespace net {
@ -100,6 +102,7 @@ HttpBaseChannel::HttpBaseChannel()
, mTransferSize(0) , mTransferSize(0)
, mDecodedBodySize(0) , mDecodedBodySize(0)
, mEncodedBodySize(0) , mEncodedBodySize(0)
, mContentWindowId(0)
, mRequireCORSPreflight(false) , mRequireCORSPreflight(false)
, mReportCollector(new ConsoleReportCollector()) , mReportCollector(new ConsoleReportCollector())
, mForceMainDocumentChannel(false) , mForceMainDocumentChannel(false)
@ -1100,6 +1103,30 @@ HttpBaseChannel::nsContentEncodings::PrepareForNext(void)
// HttpBaseChannel::nsIHttpChannel // HttpBaseChannel::nsIHttpChannel
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
{
if (!mContentWindowId) {
nsCOMPtr<nsILoadContext> loadContext;
GetCallback(loadContext);
if (loadContext) {
nsCOMPtr<nsIDOMWindow> topWindow;
loadContext->GetTopWindow(getter_AddRefs(topWindow));
nsCOMPtr<nsIDOMWindowUtils> windowUtils = do_GetInterface(topWindow);
if (windowUtils) {
windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
}
}
}
*aWindowId = mContentWindowId;
return NS_OK;
}
NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
{
mContentWindowId = aWindowId;
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize) HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
{ {

View File

@ -178,6 +178,8 @@ public:
NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override; NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override; NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override; NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
// nsIHttpChannelInternal // nsIHttpChannelInternal
NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override; NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override;
@ -496,6 +498,10 @@ protected:
nsID mSchedulingContextID; nsID mSchedulingContextID;
bool EnsureSchedulingContextID(); bool EnsureSchedulingContextID();
// ID of the top-level document's inner window this channel is being
// originated from.
uint64_t mContentWindowId;
bool mRequireCORSPreflight; bool mRequireCORSPreflight;
nsTArray<nsCString> mUnsafeHeaders; nsTArray<nsCString> mUnsafeHeaders;

View File

@ -36,6 +36,8 @@
#include "nsContentSecurityManager.h" #include "nsContentSecurityManager.h"
#include "nsIDeprecationWarner.h" #include "nsIDeprecationWarner.h"
#include "nsICompressConvStats.h" #include "nsICompressConvStats.h"
#include "nsIDocument.h"
#include "nsIDOMWindowUtils.h"
#ifdef OS_POSIX #ifdef OS_POSIX
#include "chrome/common/file_descriptor_set_posix.h" #include "chrome/common/file_descriptor_set_posix.h"
@ -1870,6 +1872,18 @@ HttpChannelChild::ContinueAsyncOpen()
return NS_ERROR_ILLEGAL_VALUE; return NS_ERROR_ILLEGAL_VALUE;
} }
// This id identifies the inner window's top-level document,
// which changes on every new load or navigation.
uint64_t contentWindowId = 0;
if (tabChild) {
MOZ_ASSERT(tabChild->WebNavigation());
nsCOMPtr<nsIDocument> document = tabChild->GetDocument();
if (document) {
contentWindowId = document->InnerWindowID();
}
}
SetTopLevelContentWindowId(contentWindowId);
HttpChannelOpenArgs openArgs; HttpChannelOpenArgs openArgs;
// No access to HttpChannelOpenArgs members, but they each have a // No access to HttpChannelOpenArgs members, but they each have a
// function with the struct name that returns a ref. // function with the struct name that returns a ref.
@ -1970,6 +1984,8 @@ HttpChannelChild::ContinueAsyncOpen()
mSchedulingContextID.ToProvidedString(scid); mSchedulingContextID.ToProvidedString(scid);
openArgs.schedulingContextID().AssignASCII(scid); openArgs.schedulingContextID().AssignASCII(scid);
openArgs.contentWindowId() = contentWindowId;
// The socket transport in the chrome process now holds a logical ref to us // The socket transport in the chrome process now holds a logical ref to us
// until OnStopRequest, or we do a redirect, or we hit an IPDL error. // until OnStopRequest, or we do a redirect, or we hit an IPDL error.
AddIPDLReference(); AddIPDLReference();

View File

@ -133,7 +133,8 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
a.loadInfo(), a.synthesizedResponseHead(), a.loadInfo(), a.synthesizedResponseHead(),
a.synthesizedSecurityInfoSerialization(), a.synthesizedSecurityInfoSerialization(),
a.cacheKey(), a.schedulingContextID(), a.preflightArgs(), a.cacheKey(), a.schedulingContextID(), a.preflightArgs(),
a.initialRwin(), a.suspendAfterSynthesizeResponse()); a.initialRwin(), a.suspendAfterSynthesizeResponse(),
a.contentWindowId());
} }
case HttpChannelCreationArgs::THttpChannelConnectArgs: case HttpChannelCreationArgs::THttpChannelConnectArgs:
{ {
@ -386,7 +387,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
const nsCString& aSchedulingContextID, const nsCString& aSchedulingContextID,
const OptionalCorsPreflightArgs& aCorsPreflightArgs, const OptionalCorsPreflightArgs& aCorsPreflightArgs,
const uint32_t& aInitialRwin, const uint32_t& aInitialRwin,
const bool& aSuspendAfterSynthesizeResponse) const bool& aSuspendAfterSynthesizeResponse,
const uint64_t& aContentWindowId)
{ {
nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
if (!uri) { if (!uri) {
@ -440,6 +442,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
return SendFailedAsyncOpen(rv); return SendFailedAsyncOpen(rv);
mChannel = static_cast<nsHttpChannel *>(channel.get()); mChannel = static_cast<nsHttpChannel *>(channel.get());
mChannel->SetTopLevelContentWindowId(aContentWindowId);
mChannel->SetWarningReporter(this); mChannel->SetWarningReporter(this);
mChannel->SetTimingEnabled(true); mChannel->SetTimingEnabled(true);
if (mPBOverride != kPBOverride_Unset) { if (mPBOverride != kPBOverride_Unset) {

View File

@ -129,7 +129,8 @@ protected:
const nsCString& aSchedulingContextID, const nsCString& aSchedulingContextID,
const OptionalCorsPreflightArgs& aCorsPreflightArgs, const OptionalCorsPreflightArgs& aCorsPreflightArgs,
const uint32_t& aInitialRwin, const uint32_t& aInitialRwin,
const bool& aSuspendAfterSynthesizeResponse); const bool& aSuspendAfterSynthesizeResponse,
const uint64_t& aContentWindowId);
virtual bool RecvSetPriority(const uint16_t& priority) override; virtual bool RecvSetPriority(const uint16_t& priority) override;
virtual bool RecvSetClassOfService(const uint32_t& cos) override; virtual bool RecvSetClassOfService(const uint32_t& cos) override;

View File

@ -56,6 +56,18 @@ NullHttpChannel::Init(nsIURI *aURI,
// NullHttpChannel::nsIHttpChannel // NullHttpChannel::nsIHttpChannel
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
NS_IMETHODIMP
NullHttpChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
NullHttpChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP NS_IMETHODIMP
NullHttpChannel::GetTransferSize(uint64_t *aTransferSize) NullHttpChannel::GetTransferSize(uint64_t *aTransferSize)
{ {

View File

@ -384,4 +384,10 @@ interface nsIHttpChannel : nsIChannel
* Identifies the scheduling context for this load. * Identifies the scheduling context for this load.
*/ */
[noscript] attribute nsID schedulingContextID; [noscript] attribute nsID schedulingContextID;
/**
* ID of the top-level document's inner window. Identifies the content
* this channel is being loaded in.
*/
attribute uint64_t topLevelContentWindowId;
}; };

View File

@ -721,6 +721,21 @@ nsViewSourceChannel::OnDataAvailable(nsIRequest *aRequest, nsISupports* aContext
// We want to forward most of nsIHttpChannel over to mHttpChannel, but we want // We want to forward most of nsIHttpChannel over to mHttpChannel, but we want
// to override GetRequestHeader and VisitHeaders. The reason is that we don't // to override GetRequestHeader and VisitHeaders. The reason is that we don't
// want various headers like Link: and Refresh: applying to view-source. // want various headers like Link: and Refresh: applying to view-source.
NS_IMETHODIMP
nsViewSourceChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
{
return !mHttpChannel ? NS_ERROR_NULL_POINTER :
mHttpChannel->GetTopLevelContentWindowId(aWindowId);
}
NS_IMETHODIMP
nsViewSourceChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
{
return !mHttpChannel ? NS_ERROR_NULL_POINTER :
mHttpChannel->SetTopLevelContentWindowId(aWindowId);
}
NS_IMETHODIMP NS_IMETHODIMP
nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod) nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod)
{ {

View File

@ -95,30 +95,64 @@ LoginManagerPromptFactory.prototype = {
return; return;
} }
this._asyncPromptInProgress = true; // Set up a counter for ensuring that the basic auth prompt can not
prompt.inProgress = true; // 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.canceledAuthenticationPromptCounter) {
browser.canceledAuthenticationPromptCounter = {};
}
if (!browser.canceledAuthenticationPromptCounter[baseDomain]) {
browser.canceledAuthenticationPromptCounter[baseDomain] = 0;
}
}
var self = this; var self = this;
var runnable = { var runnable = {
cancel: false,
run : function() { run : function() {
var ok = false; var ok = false;
try { if (!this.cancel) {
self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'"); try {
ok = prompter.promptAuth(prompt.channel, self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
prompt.level, ok = prompter.promptAuth(prompt.channel,
prompt.authInfo); prompt.level,
} catch (e if (e instanceof Components.Exception) && prompt.authInfo);
e.result == Cr.NS_ERROR_NOT_AVAILABLE) { } catch (e) {
self.log("_doAsyncPrompt:run bypassed, UI is not available in this context"); if (e instanceof Components.Exception &&
} catch (e) { e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
Components.utils.reportError("LoginManagerPrompter: " + self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
"_doAsyncPrompt:run: " + e + "\n"); } else {
} Components.utils.reportError("LoginManagerPrompter: " +
"_doAsyncPrompt:run: " + e + "\n");
}
}
delete self._asyncPrompts[hashKey]; delete self._asyncPrompts[hashKey];
prompt.inProgress = false; prompt.inProgress = false;
self._asyncPromptInProgress = false; self._asyncPromptInProgress = false;
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.canceledAuthenticationPromptCounter[baseDomain] = 0;
} else {
browser.canceledAuthenticationPromptCounter[baseDomain] += 1;
}
}
}
for (var consumer of prompt.consumers) { for (var consumer of prompt.consumers) {
if (!consumer.callback) if (!consumer.callback)
@ -128,16 +162,37 @@ LoginManagerPromptFactory.prototype = {
self.log("Calling back to " + consumer.callback + " ok=" + ok); self.log("Calling back to " + consumer.callback + " ok=" + ok);
try { try {
if (ok) if (ok) {
consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo); consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
else } else {
consumer.callback.onAuthCancelled(consumer.context, true); consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
}
} catch (e) { /* Throw away exceptions caused by callback */ } } catch (e) { /* Throw away exceptions caused by callback */ }
} }
self._doAsyncPrompt(); self._doAsyncPrompt();
} }
}; };
var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");
if (browser) {
let cancelationCounter = browser.canceledAuthenticationPromptCounter[baseDomain];
this.log("cancelationCounter ="+ cancelationCounter);
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 {
this._asyncPromptInProgress = true;
prompt.inProgress = true;
}
Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
this.log("_doAsyncPrompt:run dispatched"); this.log("_doAsyncPrompt:run dispatched");
}, },