Open Bug 1944926 Opened 7 months ago Updated 6 months ago

Executing arbitrary JavaScript from third-party origin when processing HTTP Basic Auth

Categories

(Core :: DOM: Navigation, defect)

Firefox 128
defect

Tracking

()

People

(Reporter: sysmustang, Unassigned)

References

Details

(Keywords: csectype-spoof, reporter-external)

Attachments

(2 files)

Attached file firefox_bug-main.zip

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0

Steps to reproduce:

Using Firefox Version 115.13.0esr (64-bit) and 134.0.2 (64-bit)
You can review quick demo in the attacker.gif attachment. Please unpack zip archive to view PoC. Also, you can find full report in the Submission.md file.

A remote attacker can trick the Firefox browser into mismatching URL and site identity button info, additionally, the attacker can execute limited malicious JavaScript code during the basic authentication stage on a 3rd party website. Please see below for a detailed explanation.

Steps:

  1. Edit /etc/hosts and add following entries:
    127.0.0.1 attacker.local
    127.0.0.1 thirdparty.local

  2. Run "python3 attacker.py"
    Open Firefox
    Open new tab and the console (Ctrl + Shift + K for Windows/Linix and Cmd + Option + K for Mac)
    Open "attacker.local:8080" in the new tab you just opened
    When redirected to the "thirdparty.local" page, observe the file download (with a 2 second delay)
    Compare what is displayed in the URL bar and in the site identity buttons info
    Provide any credentials in the basic auth

Tech details:

The root of the issue is that when redirecting to basic authentication on a domain we don’t control, the origin remains unchanged until the authentication is successfully completed. In this case JavaScript execution is paused, but HTTP XMLHTTPRequest events are still triggered when their state changes. The exploitation chain is as follows:

Send an asynchronous HTTP request to the /sleep endpoint and set up a response event handler function:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/sleep', true); // sleep 2 second
xhr.onreadystatechange = function() {
// this event will be triggered once redirect to third party resource happens
}

Redirect to third-party basic auth: document.location = 'http://microsoft.com/basic_auth'. Once the redirect is performed, the event handler will be triggered immediately, allowing us to execute arbitrary JavaScript code in this event handler while the URL displayed in the browser will be different.
To wait for the basic authentication window to render, we can use a synchronous XHR request to the /sleep endpoint.

var sleepxhr = new XMLHttpRequest();
sleepxhr.open('GET', '/sleep', false);
sleepxhr.send();

Executing the window.stop() method terminates the basic authentication process. Regardless of the credentials entered, the authentication window disappears immediately after clicking “Login” or “Cancel.”
Trigger the download_file() function to save the file. Since the URL displayed belongs to a different domain, the user will assume that the file is coming from a third-party site not under our control.

You can file entire code in the templates/index.html file.

This is just one possible exploit scenario. You can also execute arbitrary JavaScript in this event handler.

Actual results:

Firefox allows JavaScript to be executed from the attacker's domain when user is redirected to the basic authentication page of a third-party website from attacker domain. Firefox displays attacker's URL when the user clicks on the site identity button (identity block) but shows the third-party URL in the URL bar.

Expected results:

Firefox should not execute JavaScript from the attacker's domain when redirected to the basic authentication page of a third-party website from attackers domain.
Firefox should display the correct URL when the user clicks on the site identity button (identity block). This URL should match the one shown to the user in the URL bar. For example, in the case of attacker.gif, the identity block should display the same URI as shown in the URL bar.

Attached image attacker.gif

Executing arbitrary JavaScript from third-party origin

To be clear, you are at all times running the JS on attacker.local, right? There doesn't seem to be any claim that you're running the JS that originates from attacker.local with the domain/origin/principal of thirdparty.local (ie you're not breaking SOP / achieving XSS) - only that the other domain is what is in the address bar and (allegedly) that this is confusing for the user?

In practice, other than downloaded files, how would something like this be exploited, given the user cannot interact with the page that is executing the JS, and the page could execute JS before? Assuming I'm understanding this correctly, the idea is that there is some spoofing ability - but there is no ability to "take advantage" of the spoofing, all the more because as you noted, the old/attacker page will unload as soon as the http auth dialog is closed. So I struggle to see how this would affect real users.

Flags: needinfo?(sysmustang)

This really feels pretty "damned if you do, damned if you don't" - people were upset when the url bar didn't reflect the http auth domain, and when we finally implemented that, now the issue is that it does reflect that domain. I'm not sure I really believe that the download thing would genuinely trick people more than just starting the file download and then navigating straight to trustedwebsite.com without the http auth (bug 249673), but oh well.

When we added the address bar updates in bug 791594, there was discussion (bug 791594 comment 12 and later) about the timing of the unload event. But unfortunately it doesn't seem like this was pursued. Sean, I see that you've made some recent docshell http auth related changes - are you familiar with the sequence of events here, and how hard/easy it would be to "finish" unloading and JS execution before showing the http auth prompt?

Flags: needinfo?(sekim)

Yes, you correctly pointed out that this is not an SOP bypass. The issue lies in the fact that JavaScript can be executed from attacker.local, while the browser’s address bar displays thirdparty.local.

It is important to note that calling window.stop() cancels HTTP Basic Auth. This means that after entering valid credentials on thirdparty.com and clicking the Login button, the user is returned to attacker.local. As a result, the user may mistakenly believe that after successful authentication, they were brought back to a page asking them to review a document that was downloaded while the Basic Auth prompt was displayed. Consequently, they might think that the legitimate site is requesting them to open a downloaded “update” after entering their valid login credentials.

From a practical perspective, this technique can be leveraged to enhance phishing attacks. HTTP Basic Auth is still widely used in many companies, as it remains a popular authentication method. For example, one of the GIF images demonstrates such an attack on a Microsoft domain.

Flags: needinfo?(sysmustang)
See Also: → 1763671

Actually, Bug 1763671 introduced a pref (network.http.basic_http_auth.enabled) to disable basic HTTP auth (and simply show the error page before executing JavaScript). However, we decided to enable it by default because basic http auth can still be a viable option for many users (as you mentioned). I am not entirely certain on the scope of this bug, but I think we can prevent the execution by flipping network.http.basic_http_auth.enabled (which is optional).

https://searchfox.org/mozilla-central/rev/548b6981501f59e3c9f2f7851c013e7d53c4e72f/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp#644-649

Flags: needinfo?(sekim)

I'm not sure I really believe that the download thing would genuinely trick people more than just starting the file download and then navigating straight to trustedwebsite.com without the http auth (bug 249673), but oh well.

Just to add to what Philip said earlier, @Gijs: The difference between bug 249673 and this bug is that we (the attackers) can control the timing of when the file download starts. As a result, the download begins only after the user has already landed on trustedwebsite.com.
From the end user’s perspective, they see they are on trustedwebsite.com, and then—after they’ve arrived—the file download begins. After that as Philip said: "the user may mistakenly believe that, after successful authentication, they were brought back to a page prompting them to review a document that was downloaded while the Basic Auth prompt was displayed."

See Also: → 360871

(In reply to Sean Kim from comment #5)

Actually, Bug 1763671 introduced a pref (network.http.basic_http_auth.enabled) to disable basic HTTP auth (and simply show the error page before executing JavaScript). However, we decided to enable it by default because basic http auth can still be a viable option for many users (as you mentioned). I am not entirely certain on the scope of this bug, but I think we can prevent the execution by flipping network.http.basic_http_auth.enabled (which is optional).

https://searchfox.org/mozilla-central/rev/548b6981501f59e3c9f2f7851c013e7d53c4e72f/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp#644-649

As you say it probably is not practical to turn off http auth for everyone in order to fix this bug.

So my question is about the timing of unload events when http auth happens, and if we can force unloads at the docshell level when we hit an http auth prompt for the next load as discussed in bug 791594 (but not implemented) and, in the case of this particular exploit, unload before/without executing the window.stop and whatever other JS, in order to break the attack. Can you help with that? If not, please can you redirect to someone on the docshell / dom navigation side who could? Thank you!

The fact that XHR events fire while modal dialogs are shown is a very old publicly known bug (bug 360871).

Flags: needinfo?(sekim)

(In reply to :Gijs (he/him) from comment #7)

(In reply to Sean Kim from comment #5)

Actually, Bug 1763671 introduced a pref (network.http.basic_http_auth.enabled) to disable basic HTTP auth (and simply show the error page before executing JavaScript). However, we decided to enable it by default because basic http auth can still be a viable option for many users (as you mentioned). I am not entirely certain on the scope of this bug, but I think we can prevent the execution by flipping network.http.basic_http_auth.enabled (which is optional).

https://searchfox.org/mozilla-central/rev/548b6981501f59e3c9f2f7851c013e7d53c4e72f/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp#644-649

As you say it probably is not practical to turn off http auth for everyone in order to fix this bug.

So my question is about the timing of unload events when http auth happens, and if we can force unloads at the docshell level when we hit an http auth prompt for the next load as discussed in bug 791594 (but not implemented) and, in the case of this particular exploit, unload before/without executing the window.stop and whatever other JS, in order to break the attack. Can you help with that? If not, please can you redirect to someone on the docshell / dom navigation side who could? Thank you!

The fact that XHR events fire while modal dialogs are shown is a very old publicly known bug (bug 360871).

Hello Nika,

I was wondering if we could force unloads at the docshell level when encountering an http auth prompt during the next load as Gijs mentioned here? I am not very familiar with docshell, it would be great to receive input from someone experienced in DOM navigation. Thanks!

Flags: needinfo?(sekim) → needinfo?(nika)

I'm not entirely certain what is meant by "forcing unloads" in this scenario. I vaguely remember discussing the possibility of changing how HTTP basic auth works for navigations such that we'd load the 401 response, then show the HTTP basic auth dialog on top of that page, and then do the authenticated load as a separate navigation, which would have the effect of causing the navigated-from page to unload so we can display the 401 response, is that what you're thinking of?

In general a docshell can't hold no content, but it might be possible to load an empty about:blank document, which would cause the previous page to unload as well, though it'd be important to make sure the original navigation still goes through in this situation, and replaces the about:blank page in session history as expected.

Flags: needinfo?(nika)

Per discussion in triage, we think that all reasonable fixes here are in platform code, one or more of:

  • unload the original page before triggering http auth (e.g. the 401 rendering or the about:blank suggestion in comment 9 - from a security pov either would work)
  • break the xhr callbacks running in this situation so no JS can run
  • abort download requests triggered by the page before it navigated away (though that likely has other repercussions)
  • disable http auth by default

The frontend was updated to show the http auth origin, but it cannot show both that and the page origin so there is no way to put more lipstick on this particular pig.

Group: firefox-core-security → dom-core-security
Component: Untriaged → DOM: Navigation
Product: Firefox → Core

Disabling http auth by default was challenging since we pretty much need to flip the pref manually for all the tests that use basic http auth (I tried to do so by default). Also, I think it makes more sense to keep the basic http auth pref as optional (should be dealt via enterprise policy).

(In reply to Sean Kim from comment #5)

Actually, Bug 1763671 introduced a pref (network.http.basic_http_auth.enabled) to disable basic HTTP auth (and simply show the error page before executing JavaScript).

This bug keeps talking about "Basic" Auth, but that's not the problem, right? Any form of HTTP Auth should behave the same. If you disable the "basic auth" pref it won't disable HTTP Auth for any site using Digest Auth, for example. At one point we expected every site to switch from the insecure Basic Auth to Digest (thus the pref), but in the end most sites were satisfied that deploying TLS on top was good enough.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Severity: -- → S3

Hello,
Alikhan and I would like to ask about the correct process for obtaining a CVE for this issue. Additionally, when would it be appropriate to discuss this issue publicly?

(In reply to Okhonko Philip from comment #13)

Hello,
Alikhan and I would like to ask about the correct process for obtaining a CVE for this issue. Additionally, when would it be appropriate to discuss this issue publicly?

--> :tjr

Flags: needinfo?(tom)

We've decided to remove the security rating and unhide the bug. This behavior is odd and could confuse the user, and we consider it a bug, but not a security bug. The identity information available to the user is accurate, and the general technique can be accomplished in other ways via a few other ways including tabnapping type techniques and navigating opened popups or tabs - all of which are valid if potentially deceptive uses of the Web APIs.

Group: dom-core-security
Flags: needinfo?(tom)
Keywords: sec-low
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: