#547: block modal HTTP auth DOS M1312243 M377496 + glue code
This commit is contained in:
parent
9465ea36d8
commit
d60d9721fd
|
@ -3259,6 +3259,10 @@ function BrowserReloadWithFlags(reloadFlags) {
|
|||
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)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
|
|
|
@ -2679,6 +2679,7 @@ let remote = false;
|
|||
<parameter name="aTab"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
delete this.getBrowserForTab(aTab).canceledAuthenticationPromptCounter;
|
||||
this.getBrowserForTab(aTab).reload();
|
||||
]]>
|
||||
</body>
|
||||
|
|
|
@ -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
|
||||
// area when the current tab is re-selected.
|
||||
gBrowser.selectedBrowser.focus();
|
||||
delete gBrowser.selectedBrowser.canceledAuthenticationPromptCounter;
|
||||
|
||||
let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
#include "nsWhitespaceTokenizer.h"
|
||||
#include "nsICookieService.h"
|
||||
#include "nsIConsoleReportCollector.h"
|
||||
#include "nsILoginManagerPrompter.h"
|
||||
|
||||
// we want to explore making the document own 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
|
||||
// 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,
|
||||
reinterpret_cast<void**>(aResult));
|
||||
|
|
|
@ -5151,6 +5151,12 @@ pref("toolkit.pageThumbs.screenSizeDivisor", 7);
|
|||
pref("toolkit.pageThumbs.minWidth", 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.logging.enabled", false);
|
||||
pref("tenfourfox.dom.forms.date", true);
|
||||
|
|
|
@ -117,6 +117,7 @@ struct HttpChannelOpenArgs
|
|||
OptionalCorsPreflightArgs preflightArgs;
|
||||
uint32_t initialRwin;
|
||||
bool suspendAfterSynthesizeResponse;
|
||||
uint64_t contentWindowId;
|
||||
};
|
||||
|
||||
struct HttpChannelConnectArgs
|
||||
|
|
|
@ -48,8 +48,10 @@
|
|||
#include "LoadInfo.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "HttpBaseChannel.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
@ -100,6 +102,7 @@ HttpBaseChannel::HttpBaseChannel()
|
|||
, mTransferSize(0)
|
||||
, mDecodedBodySize(0)
|
||||
, mEncodedBodySize(0)
|
||||
, mContentWindowId(0)
|
||||
, mRequireCORSPreflight(false)
|
||||
, mReportCollector(new ConsoleReportCollector())
|
||||
, mForceMainDocumentChannel(false)
|
||||
|
@ -1100,6 +1103,30 @@ HttpBaseChannel::nsContentEncodings::PrepareForNext(void)
|
|||
// 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
|
||||
HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
|
||||
{
|
||||
|
|
|
@ -178,6 +178,8 @@ public:
|
|||
NS_IMETHOD GetIsMainDocumentChannel(bool* aValue) override;
|
||||
NS_IMETHOD SetIsMainDocumentChannel(bool aValue) override;
|
||||
NS_IMETHOD GetProtocolVersion(nsACString & aProtocolVersion) override;
|
||||
NS_IMETHOD GetTopLevelContentWindowId(uint64_t *aContentWindowId) override;
|
||||
NS_IMETHOD SetTopLevelContentWindowId(uint64_t aContentWindowId) override;
|
||||
|
||||
// nsIHttpChannelInternal
|
||||
NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI) override;
|
||||
|
@ -496,6 +498,10 @@ protected:
|
|||
nsID mSchedulingContextID;
|
||||
bool EnsureSchedulingContextID();
|
||||
|
||||
// ID of the top-level document's inner window this channel is being
|
||||
// originated from.
|
||||
uint64_t mContentWindowId;
|
||||
|
||||
bool mRequireCORSPreflight;
|
||||
nsTArray<nsCString> mUnsafeHeaders;
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
#include "nsContentSecurityManager.h"
|
||||
#include "nsIDeprecationWarner.h"
|
||||
#include "nsICompressConvStats.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDOMWindowUtils.h"
|
||||
|
||||
#ifdef OS_POSIX
|
||||
#include "chrome/common/file_descriptor_set_posix.h"
|
||||
|
@ -1870,6 +1872,18 @@ HttpChannelChild::ContinueAsyncOpen()
|
|||
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;
|
||||
// No access to HttpChannelOpenArgs members, but they each have a
|
||||
// function with the struct name that returns a ref.
|
||||
|
@ -1970,6 +1984,8 @@ HttpChannelChild::ContinueAsyncOpen()
|
|||
mSchedulingContextID.ToProvidedString(scid);
|
||||
openArgs.schedulingContextID().AssignASCII(scid);
|
||||
|
||||
openArgs.contentWindowId() = contentWindowId;
|
||||
|
||||
// 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.
|
||||
AddIPDLReference();
|
||||
|
|
|
@ -133,7 +133,8 @@ HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs)
|
|||
a.loadInfo(), a.synthesizedResponseHead(),
|
||||
a.synthesizedSecurityInfoSerialization(),
|
||||
a.cacheKey(), a.schedulingContextID(), a.preflightArgs(),
|
||||
a.initialRwin(), a.suspendAfterSynthesizeResponse());
|
||||
a.initialRwin(), a.suspendAfterSynthesizeResponse(),
|
||||
a.contentWindowId());
|
||||
}
|
||||
case HttpChannelCreationArgs::THttpChannelConnectArgs:
|
||||
{
|
||||
|
@ -386,7 +387,8 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
|
|||
const nsCString& aSchedulingContextID,
|
||||
const OptionalCorsPreflightArgs& aCorsPreflightArgs,
|
||||
const uint32_t& aInitialRwin,
|
||||
const bool& aSuspendAfterSynthesizeResponse)
|
||||
const bool& aSuspendAfterSynthesizeResponse,
|
||||
const uint64_t& aContentWindowId)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
|
||||
if (!uri) {
|
||||
|
@ -440,6 +442,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
|
|||
return SendFailedAsyncOpen(rv);
|
||||
|
||||
mChannel = static_cast<nsHttpChannel *>(channel.get());
|
||||
mChannel->SetTopLevelContentWindowId(aContentWindowId);
|
||||
mChannel->SetWarningReporter(this);
|
||||
mChannel->SetTimingEnabled(true);
|
||||
if (mPBOverride != kPBOverride_Unset) {
|
||||
|
|
|
@ -129,7 +129,8 @@ protected:
|
|||
const nsCString& aSchedulingContextID,
|
||||
const OptionalCorsPreflightArgs& aCorsPreflightArgs,
|
||||
const uint32_t& aInitialRwin,
|
||||
const bool& aSuspendAfterSynthesizeResponse);
|
||||
const bool& aSuspendAfterSynthesizeResponse,
|
||||
const uint64_t& aContentWindowId);
|
||||
|
||||
virtual bool RecvSetPriority(const uint16_t& priority) override;
|
||||
virtual bool RecvSetClassOfService(const uint32_t& cos) override;
|
||||
|
|
|
@ -56,6 +56,18 @@ NullHttpChannel::Init(nsIURI *aURI,
|
|||
// 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
|
||||
NullHttpChannel::GetTransferSize(uint64_t *aTransferSize)
|
||||
{
|
||||
|
|
|
@ -384,4 +384,10 @@ interface nsIHttpChannel : nsIChannel
|
|||
* Identifies the scheduling context for this load.
|
||||
*/
|
||||
[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;
|
||||
};
|
||||
|
|
|
@ -721,6 +721,21 @@ nsViewSourceChannel::OnDataAvailable(nsIRequest *aRequest, nsISupports* aContext
|
|||
// 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
|
||||
// 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
|
||||
nsViewSourceChannel::GetRequestMethod(nsACString & aRequestMethod)
|
||||
{
|
||||
|
|
|
@ -95,30 +95,64 @@ LoginManagerPromptFactory.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this._asyncPromptInProgress = true;
|
||||
prompt.inProgress = true;
|
||||
// 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.canceledAuthenticationPromptCounter) {
|
||||
browser.canceledAuthenticationPromptCounter = {};
|
||||
}
|
||||
|
||||
if (!browser.canceledAuthenticationPromptCounter[baseDomain]) {
|
||||
browser.canceledAuthenticationPromptCounter[baseDomain] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var runnable = {
|
||||
cancel: false,
|
||||
run : function() {
|
||||
var ok = false;
|
||||
try {
|
||||
self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
|
||||
ok = prompter.promptAuth(prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo);
|
||||
} catch (e if (e instanceof Components.Exception) &&
|
||||
e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
|
||||
self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
|
||||
} catch (e) {
|
||||
Components.utils.reportError("LoginManagerPrompter: " +
|
||||
"_doAsyncPrompt:run: " + e + "\n");
|
||||
}
|
||||
if (!this.cancel) {
|
||||
try {
|
||||
self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
|
||||
ok = prompter.promptAuth(prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo);
|
||||
} catch (e) {
|
||||
if (e instanceof Components.Exception &&
|
||||
e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
|
||||
self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
|
||||
} else {
|
||||
Components.utils.reportError("LoginManagerPrompter: " +
|
||||
"_doAsyncPrompt:run: " + e + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
delete self._asyncPrompts[hashKey];
|
||||
prompt.inProgress = false;
|
||||
self._asyncPromptInProgress = false;
|
||||
delete self._asyncPrompts[hashKey];
|
||||
prompt.inProgress = 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) {
|
||||
if (!consumer.callback)
|
||||
|
@ -128,16 +162,37 @@ LoginManagerPromptFactory.prototype = {
|
|||
|
||||
self.log("Calling back to " + consumer.callback + " ok=" + ok);
|
||||
try {
|
||||
if (ok)
|
||||
if (ok) {
|
||||
consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
|
||||
else
|
||||
consumer.callback.onAuthCancelled(consumer.context, true);
|
||||
} else {
|
||||
consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
|
||||
}
|
||||
} catch (e) { /* Throw away exceptions caused by callback */ }
|
||||
}
|
||||
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);
|
||||
this.log("_doAsyncPrompt:run dispatched");
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue