Bug 1614969 - Check download with MixedContentBlocker r=ckerschb

Differential Revision: https://phabricator.services.mozilla.com/D73302
This commit is contained in:
Sebastian Streich 2020-07-08 15:25:43 +00:00
parent c3f3931f8c
commit 8ccf28a8ba
11 changed files with 255 additions and 3 deletions

View File

@ -19,6 +19,9 @@ add_task(async function setup() {
// cookies. This is incompatible with the cookie sameSite schemeful
// feature and we need to disable it.
["network.cookie.sameSite.schemeful", false],
// This Test trys to download mixed content
// so we need to make sure that this is not blocked
["dom.block_download_insecure", false],
],
});
});

View File

@ -45,6 +45,9 @@ InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://
LoadingMixedActiveContent2=Loading mixed (insecure) active content “%1$S” on a secure page
LoadingMixedDisplayContent2=Loading mixed (insecure) display content “%1$S” on a secure page
LoadingMixedDisplayObjectSubrequestDeprecation=Loading mixed (insecure) content “%1$S” within a plugin on a secure page is discouraged and will be blocked soon.
# LOCALIZATION NOTE: "%S" is the URI of the insecure mixed content download
MixedContentBlockedDownload = Blocked downloading insecure content “%S”.
# LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
# LOCALIZATION NOTE: Do not translate "allow-top-navigation-by-user-activation", "allow-top-navigation", "sandbox" or "iframe"

View File

@ -22,6 +22,7 @@
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/Document.h"
#include "LoadInfo.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_dom.h"
@ -1075,3 +1076,79 @@ bool nsContentSecurityUtils::ValidateScriptFilename(const char* aFilename,
// builds and return false to prevent execution in non-debug builds.
return true;
}
/* static */
void nsContentSecurityUtils::LogMessageToConsole(nsIHttpChannel* aChannel,
const char* aMsg) {
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return;
}
uint64_t windowID = 0;
rv = aChannel->GetTopLevelContentWindowId(&windowID);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (!windowID) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
loadInfo->GetInnerWindowID(&windowID);
}
nsAutoString localizedMsg;
nsAutoCString spec;
uri->GetSpec(spec);
AutoTArray<nsString, 1> params = {NS_ConvertUTF8toUTF16(spec)};
rv = nsContentUtils::FormatLocalizedString(
nsContentUtils::eSECURITY_PROPERTIES, aMsg, params, localizedMsg);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsContentUtils::ReportToConsoleByWindowID(
localizedMsg, nsIScriptError::warningFlag, "Security"_ns, windowID, uri);
}
/* static */
bool nsContentSecurityUtils::IsDownloadAllowed(
nsIChannel* aChannel, const nsAutoCString& aMimeTypeGuess) {
MOZ_ASSERT(aChannel, "IsDownloadAllowed without channel?");
if (!StaticPrefs::dom_block_download_insecure()) {
return true;
}
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
return true;
}
nsCOMPtr<nsIURI> contentLocation;
aChannel->GetURI(getter_AddRefs(contentLocation));
nsCOMPtr<nsIPrincipal> loadingPrincipal = loadInfo->GetLoadingPrincipal();
if (!loadingPrincipal) {
loadingPrincipal = loadInfo->TriggeringPrincipal();
}
// Creating a fake Loadinfo that is just used for the MCB check.
nsCOMPtr<nsILoadInfo> secCheckLoadInfo =
new LoadInfo(loadingPrincipal, loadInfo->TriggeringPrincipal(), nullptr,
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
nsIContentPolicy::TYPE_OTHER);
int16_t decission = nsIContentPolicy::ACCEPT;
nsMixedContentBlocker::ShouldLoad(false, // aHadInsecureImageRedirect
contentLocation, // aContentLocation,
secCheckLoadInfo, // aLoadinfo
aMimeTypeGuess, // aMimeGuess,
&decission // aDecision
);
if (decission == nsIContentPolicy::ACCEPT) {
return true;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
if (httpChannel) {
LogMessageToConsole(httpChannel, "MixedContentBlockedDownload");
}
return false;
}

View File

@ -50,6 +50,12 @@ class nsContentSecurityUtils {
// If any of the two disallows framing, the channel will be cancelled.
static void PerformCSPFrameAncestorAndXFOCheck(nsIChannel* aChannel);
// Helper function to Check if a Download is allowed;
static bool IsDownloadAllowed(nsIChannel* aChannel,
const nsAutoCString& aMimeTypeGuess);
// Logs an Error Message to the Console
static void LogMessageToConsole(nsIHttpChannel* aChannel, const char* aMsg);
#if defined(DEBUG)
static void AssertAboutPageHasCSP(mozilla::dom::Document* aDocument);
#endif

View File

@ -0,0 +1,6 @@
[DEFAULT]
support-files =
download_page.html
download_server.sjs
[browser_test_mixed_content_download.js]

View File

@ -0,0 +1,98 @@
let INSECURE_BASE_URL =
getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"http://example.com/"
) + "download_page.html";
let SECURE_BASE_URL =
getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"https://example.com/"
) + "download_page.html";
function shouldPromptDownload() {
// Waits until the download Prompt is shown
return new Promise((resolve, reject) => {
Services.wm.addListener({
onOpenWindow(xulWin) {
Services.wm.removeListener(this);
let win = xulWin.docShell.domWindow;
waitForFocus(() => {
if (
win.location ==
"chrome://mozapps/content/downloads/unknownContentType.xhtml"
) {
resolve();
info("Trying to close window");
win.close();
} else {
reject();
}
}, win);
},
});
});
}
const CONSOLE_ERROR_MESSAGE = "Blocked downloading insecure content";
function shouldConsoleError() {
// Waits until CONSOLE_ERROR_MESSAGE was logged
return new Promise((resolve, reject) => {
function listener(msgObj) {
let text = msgObj.message;
if (text.includes(CONSOLE_ERROR_MESSAGE)) {
Services.console.unregisterListener(listener);
resolve();
}
}
Services.console.registerListener(listener);
});
}
async function runTest(url, link, checkFunction, decscription) {
let tab = BrowserTestUtils.addTab(gBrowser, url);
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
info("Checking: " + decscription);
let checkPromise = checkFunction();
// Click the Link to trigger the download
SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => {
content.document.getElementById(contentLink).click();
});
await checkPromise;
ok(true, decscription);
BrowserTestUtils.removeTab(tab);
}
add_task(async function() {
await runTest(
INSECURE_BASE_URL,
"insecure",
shouldPromptDownload,
"Insecure -> Insecure should download"
);
await runTest(
INSECURE_BASE_URL,
"secure",
shouldPromptDownload,
"Insecure -> Secure should download"
);
await runTest(
SECURE_BASE_URL,
"insecure",
shouldConsoleError,
"Secure -> Insecure should Error"
);
await runTest(
SECURE_BASE_URL,
"secure",
shouldPromptDownload,
"Secure -> Secure should Download"
);
});

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=676619
-->
<head>
<title>Test for the download attribute</title>
</head>
<body>
hi
<script>
const host = window.location.host;
const path = location.pathname.replace("download_page.html","download_server.sjs");
const secureLink = document.createElement("a");
secureLink.href=`https://${host}/${path}`;
secureLink.download="true";
secureLink.textContent="Secure Link";
secureLink.id="secure";
const insecureLink = document.createElement("a");
insecureLink.href=`http://${host}/${path}`;
insecureLink.download="true";
insecureLink.id="insecure";
insecureLink.textContent="Not secure Link";
document.body.append(secureLink);
document.body.append(insecureLink);
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
// force the Browser to Show a Download Prompt
function handleRequest(request, response)
{
response.setHeader("Cache-Control", "no-cache", false);
response.setHeader("Content-Disposition", "attachment");
response.setHeader("Content-Type", "application/octet-stream");
response.write('🙈🙊🐵🙊');
}

View File

@ -34,4 +34,5 @@ BROWSER_CHROME_MANIFESTS += [
'csp/browser.ini',
'general/browser.ini',
'https-only/browser.ini',
'mixedcontentblocker/browser.ini'
]

View File

@ -1501,6 +1501,12 @@
value: true
mirror: always
# Block Insecure downloads from Secure Origins
- name: dom.block_download_insecure
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# Block multiple window.open() per single event.
- name: dom.block_multiple_popups
type: bool

View File

@ -46,6 +46,7 @@
#include "nsIRedirectHistoryEntry.h"
#include "nsOSHelperAppService.h"
#include "nsOSHelperAppServiceChild.h"
#include "nsContentSecurityUtils.h"
// used to access our datastore of user-configured helper applications
#include "nsIHandlerService.h"
@ -1557,6 +1558,16 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
nsresult rv;
nsAutoCString MIMEType;
if (mMimeInfo) {
mMimeInfo->GetMIMEType(MIMEType);
}
if (!nsContentSecurityUtils::IsDownloadAllowed(aChannel, MIMEType)) {
mCanceled = true;
request->Cancel(NS_ERROR_ABORT);
return NS_OK;
}
nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(request));
mIsFileChannel = fileChan != nullptr;
@ -1577,7 +1588,6 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
if (mBrowsingContext) {
mMaybeCloseWindowHelper = new MaybeCloseWindowHelper(mBrowsingContext);
mMaybeCloseWindowHelper->SetShouldCloseWindow(mShouldCloseWindow);
nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(request, &rv));
// Determine whether a new window was opened specifically for this request
if (props) {
@ -1666,8 +1676,6 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
bool alwaysAsk = true;
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
nsAutoCString MIMEType;
mMimeInfo->GetMIMEType(MIMEType);
if (alwaysAsk) {
// But we *don't* ask if this mimeInfo didn't come from
// our user configuration datastore and the user has said