Closed Bug 1614919 (CVE-2020-6823) Opened 4 years ago Closed 4 years ago

browser.identity.launchWebAuthFlow() exposes redirect_url no matter what it is.

Categories

(WebExtensions :: Request Handling, defect, P2)

defect

Tracking

(firefox-esr68- wontfix, firefox73 wontfix, firefox74+ wontfix, firefox75+ fixed)

RESOLVED FIXED
mozilla75
Tracking Status
firefox-esr68 - wontfix
firefox73 --- wontfix
firefox74 + wontfix
firefox75 + fixed

People

(Reporter: levissie, Assigned: mixedpuppy)

References

Details

(Keywords: csectype-priv-escalation, dev-doc-complete, sec-moderate, Whiteboard: sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage][adv-main75+])

Attachments

(5 files)

browser.identity.launchWebAuthFlow() always returns the URL to where is redirected in the opened webview. This is a big security issue.

When a Firefox Add-On uses the browser.identity.launchWebAuthFlow() method, the service provider's authorization URL is passed as mandatory parameter. This URL contains among other things the redirect_uri to where the service provider should redirect the user after the user has authenticated at the service provider. The authentication part of the OAuth flow all happens in a webview that Firefox automatically launches after calling launchWebAuthFlow().

The redirect_url used, should be a registered, and thus known, url at the service provider.

This redirect_url can be the url of the add-on itself (obtained by browser.identity.getRedirectURL()), in which case the user will be redirected back to the Add-On. This redirect_url can also be something else.

The current behaviour in Firefox is that the Promise returned by browser.identity.launchWebAuthFlow(), always resolves with the URL to where is redirected. Also in the case this redirect_url is not the same url as obtained by browser.identity.getRedirectURL() (the Add-On url). The result is that I can obtain the code passed in the redirect_url's queryparams, even when the redirect goes to another domain.

Possible exploit:
An attacker can exploit this when he/she knows at least 1 registered redirect_url of an app that uses OAuth (which does not have to be under his control). He can just call the browser.identity.launchWebAuthFlow("https://auth.service.provider.com/oauth/authorize?client_id=ClientId&redirect_uri=https://the.registered.redirect_uri.org/") with given parameter. It doesn't matter where the service provider redirects the users after authenticating, the Promise returned by browser.identity.launchWebAuthFlow() resolves with the redirect URL, containing the auth_code (assuming the user authenticated correctly)

How to reproduce:

  1. Setup up basic OAuth 2 server
  2. Register a redirect_uri at the server

Step 1 and 2 represent the OAuth server of an existing (not our own) application

Next steps are the Attackers actions:

  1. Make a new Firefox Add-On (only valid manifest.json and background page necessary)
  2. Call browser.identity.launchWebAuthFlow({url: "https://authserver.com/auth?redirect_uri=https://the_registered_redirect_url.com&client_id=registeredClientId", interactive: true}).then((url)=>{console.log(url)})
  3. Authenticate correctly with OAuth app
  4. See the redirect_url logged in console.

Using this method the Attacker can counterfeit an Add-On, and use the same redirect_uri as the real Add-On. But after the redirect, use the Auth code obtained. Even though the Add-On's0 id is not registered at the service provider.

Flags: sec-bounty?

Firefox version: 72.0.2 (64-bit)

os:
Ubuntu 18.04.4 LTS

The url https://eodcacbkjencjlinmmdhdmflokbmbbka.chromiumapp.org/ is registered at the OAuth provider.

After redirection, the OAuth provider redirects the user to https://eodcacbkjencjlinmmdhdmflokbmbbka.chromiumapp.org/, and the Promise resolves with "https://eodcacbkjencjlinmmdhdmflokbmbbka.chromiumapp.org/?state=6cx5s&code=DRUbG6dVa41Vq8OFDTS4TM83L58jN" which contains the code.

This allows a Firefox Add-On to obtain auth codes from redirect url's which did not redirect to the domain of the Add-On but to a totally different one.

Luca/Shane, can you take a look? I don't know enough about browser.identity to follow what's going on here.

Component: Security → Untriaged
Flags: needinfo?(mixedpuppy)
Flags: needinfo?(lgreco)
Product: Firefox → WebExtensions

Also confirmed on Firefox 73.0 (64-bits)

As you can see, following the same flow in Google Chrome, with the redirect_url registered at the service provider, but from an Extension with another id. The resolved return from the Promise is undefined and an error is thrown.

The documentation (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/identity/launchWebAuthFlow) does explain the current flow:

Return value:
A Promise. If the extension is authorized successfully, this will be fulfilled with a string containing the redirect URL. The URL will include a parameter that either is an access token or can be exchanged for an access token, using the documented flow for the particular service provider.

But this should only occur, when the user is indeed redirected to the Add-On (https://<add-on-id>.extension.allizom.org/), not when user gets redirected to any URL.

The same documentation says that redirect_uri is an optional parameter, but when I use this property, an error is thrown: Error: Type error for parameter details (Unexpected property "redirect_uri") for identity.launchWebAuthFlow.

(In reply to Leon Visscher from comment #7)

Created attachment 9126224 [details]
Screenshot from 2020-02-12 21-47-03.png

The same documentation says that redirect_uri is an optional parameter, but when I use this property, an error is thrown: Error: Type error for parameter details (Unexpected property "redirect_uri") for identity.launchWebAuthFlow.

This part seems to have been wrongly documented when the mdn API doc page has been created:
redirect_uri isn't an actual parameter of the launchWebAuthFlow method (in both Firefox, see API schema here, and Chrome, see API docs here)

The redirect_url is instead an optional parameter of the url passed from the extension into the identity.launchWebAuthFlow method.

Shane did work on this API, I'm going to discuss about this issue with Shane and we will look more deeply into it

Flags: needinfo?(lgreco)

The issue here is specifically that an addon can pass redirect_uri as a part of the url passed into launchWebAuthFlow. If the oauth service redirects to that uri, then we return the results to the addon. The api does not return information from any redirect, just redirects to the url that is provided in redirect_uri. This is by design. So the question is really whether this design is a problem.

An extension could much more easily use a proxy or webrequest listener to intercept oauth codes. It's also possible to just open a tab with an extension page that does oauth on its own without using this api and probably do exactly the same thing. Given that we generally trust addons as a part of the browser I don't really see this as a problem.

I also seem to recall that some oauth services would simply not work with the generated domains so allowing the passing of redirect_uri was necessary.

nonetheless I'll try to dig up a second opinion.

Ok, with a lengthy chat with dveditz the second opinion is that we should force redirect_uri to be the extensions uri, disallowing extensions to pass that in.

Assignee: nobody → mixedpuppy
Component: Untriaged → Request Handling
Flags: needinfo?(mixedpuppy)
Priority: -- → P2

I agree with the reporter and apparently the Chrome team. If the redirect_url isn't a match[*} of the result of getRedirectURL() then the OAuth server did not mean to give the token to that WebExtension and we should return an error. For compatibility we should match Chrome -- I assume they reject the promise, but maybe they resolve with something that is an error rather than a URL.

If a WebExtension wants to authorize as FooSite.com then it should do it from a content script running in a FooSite context.

[*] "match" is probably something like /^{getRedirectURL() result}[/?].*/ -- whatever is Chrome compat (which will be easier to figure out from their code than by experiment).

I also seem to recall that some oauth services would simply not work with the generated domains so allowing the passing of redirect_uri was necessary.

Do we know why? Would the same extension code in Chrome have the same problem?

Status: UNCONFIRMED → NEW
Type: task → defect
Ever confirmed: true

(In reply to Daniel Veditz [:dveditz] from comment #11)

I also seem to recall that some oauth services would simply not work with the generated domains so allowing the passing of redirect_uri was necessary.

Do we know why? Would the same extension code in Chrome have the same problem?

It was over 3 years ago, so memory could be bad. It might actually have been that we always included redirect_uri in the url and some services reject that.

Thanks for the quick responses and fix! I have two questions:

Is it known when/what Firefox version the fix will be released?

What is the further process for classifying the eligibility for the Client Bug Bounty program?

The fix has not been checked in yet (when it is this bug will be RESOLVED FIXED). Assuming that's soon this should be fixed in Firefox 75. Because it's a lesser severity security bug and has a risk of site breakage we are not likely to rush this into an earlier release. That also assumes that testing (particularly real-world Beta testing) goes well in terms of web compat -- if it breaks too many sites/extensions it could be back to the drawing board.

The bug bounty folks usually wait until bugs are fixed to take a look, since at that point we know exactly what the scope of the problem is. The nomination flag is on this bug so it'll get looked at. If you have further bounty questions they are best addressed in mail to security@mozilla.org since the folks working in the bug won't know.

pushing to autoland now, and will request uplift to beta after.

Group: firefox-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 4 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla75

(In reply to Daniel Veditz [:dveditz] from comment #15)

The fix has not been checked in yet (when it is this bug will be RESOLVED FIXED). Assuming that's soon this should be fixed in Firefox 75. Because it's a lesser severity security bug and has a risk of site breakage we are not likely to rush this into an earlier release. That also assumes that testing (particularly real-world Beta testing) goes well in terms of web compat -- if it breaks too many sites/extensions it could be back to the drawing board.

The bug bounty folks usually wait until bugs are fixed to take a look, since at that point we know exactly what the scope of the problem is. The nomination flag is on this bug so it'll get looked at. If you have further bounty questions they are best addressed in mail to security@mozilla.org since the folks working in the bug won't know.

I am curious how the severity of this bug (sec-moderate) is established. When I look at the security severty rating Authentication Flaws (which lead to account compromise) is listed under sec-critical.

I do believe this bug is more severe than is established right now. Exploiting this bug I can for example make an "Official Google Black Friday search extension", and make users log in with their Google accounts. I can intercept the access tokens and gain access to their account.

AFAICT, this also affects ESR68? Patch grafts cleanly there too, FWIW.

(In reply to Leon Visscher from comment #18)

I am curious how the severity of this bug (sec-moderate) is established. When I look at the security severty rating Authentication Flaws (which lead to account compromise) is listed under sec-critical.

I do believe this bug is more severe than is established right now. Exploiting this bug I can for example make an "Official Google Black Friday search extension", and make users log in with their Google accounts. I can intercept the access tokens and gain access to their account.

If you could pull this off from the general web, for all users, then this would definitely be sec-high. In this case you would first have to convince your victims to install your malicious extension. We've generally interpreted that requirement as capping the severity at sec-moderate since it would slow the spread of an attack and would get blackholed once discovered.

Whiteboard: [reporter-external] [client-bounty-form] [verif?] → sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?]
Flags: sec-bounty? → sec-bounty+

(In reply to Leon Visscher from comment #18)

I do believe this bug is more severe than is established right now. Exploiting this bug I can for example make an "Official Google Black Friday search extension", and make users log in with their Google accounts. I can intercept the access tokens and gain access to their account.

I'm not sure I understand the severity. Full extent of this vulnerability is a bad guy convincing the user to install an extension (which doesn't ask for google.com or <all_urls> permissions), and hoping that user already installed one of X other extensions with IDs known to the attacker, and that they went through the auth flow with one of them. Is that about right?

I'm not sure we've ever seen a malicious extension that doesn't already ask for <all_urls>, or at least google.com if they are named "Official Google something...".

(In reply to :Tomislav Jovanovic :zombie from comment #21)

(In reply to Leon Visscher from comment #18)

I do believe this bug is more severe than is established right now. Exploiting this bug I can for example make an "Official Google Black Friday search extension", and make users log in with their Google accounts. I can intercept the access tokens and gain access to their account.

I'm not sure I understand the severity. Full extent of this vulnerability is a bad guy convincing the user to install an extension (which doesn't ask for google.com or <all_urls> permissions), and hoping that user already installed one of X other extensions with IDs known to the attacker, and that they went through the auth flow with one of them. Is that about right?

I'm not sure we've ever seen a malicious extension that doesn't already ask for <all_urls>, or at least google.com if they are named "Official Google something...".

Hi Tomislav,

This vulnerability has nothing to do with manifest host permissions. Also there is no need for the victim to have any other extension installed.

The vulnerability is that when the attacker knows a valid redirect url and client id (and sometimes client secret) of an OAuth provider (like Google), he can develop an extension implementing this OAuth flow, even though the extension's ID is not registered (and known) at this OAuth provider.

And with OAuth implemented in browser based apps like SPAs and webextensions, these redirect url, client id and client secret are all public information.

Please request uplift to beta (and maybe esr68) when you get a chance.

Flags: needinfo?(mixedpuppy)
Regressed by: 1617885

(In reply to Julien Cristau [:jcristau] from comment #23)

Please request uplift to beta (and maybe esr68) when you get a chance.

We're debating whether this should be uplifted. This breaks some set of extensions, currently researching the situation.

Flags: needinfo?(mixedpuppy)

keeping ni?

Flags: needinfo?(mixedpuppy)
No longer regressed by: 1617885
Regressions: 1617885

Shane, any update on your research? our last beta builds in a few hours.

We are not going to uplift to beta. We'll let it ride the trains, but we will keep the patch applied.

Flags: needinfo?(mixedpuppy)

Hi Richard, putting this on your radar for this week. We'll need to update the documentation (and likely the BCD) for the identity API.

Flags: needinfo?(richard)

Thanks Caitlin, I'll await more information (i.e. my initial read of the comments didn't identify what changes might be required)

Flags: needinfo?(richard)

Was a decision made on esr68 uplift, or not just yet?

Flags: needinfo?(mixedpuppy)

(In reply to Julien Cristau [:jcristau] from comment #30)

Was a decision made on esr68 uplift, or not just yet?

I think it's fine to let this ride the train, the next ESR is not far away.

Flags: needinfo?(mixedpuppy)
Whiteboard: sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?] → sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage]
Whiteboard: sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage] → sec-high impact on affected users [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage][adv-main75+]
Attached file advisory.txt

Shane, does this advisory look accurate?

Attachment #9137348 - Flags: feedback?(mixedpuppy)

Roughly, yes.

Attachment #9137348 - Flags: feedback?(mixedpuppy) → feedback+
Alias: CVE-2020-6823

It looks like we'll need to update the documentation regarding the redirect_uri option, setting dev-doc-needed. Also, can we make this bug public?

Keywords: dev-doc-needed
Regressions: 1635344
Group: core-security-release

Could I get some advice on the documentation changes needed?

Flags: needinfo?(mixedpuppy)

Documentation review requested: MDN documentation changes have already been completed (as per Slack discussion), changes to the compatibility data are in PR 12016

Flags: needinfo?(philipp)

Now also updated the release notes and tidied up the API documentation, see PR 8363

Flags: needinfo?(philipp)
Flags: needinfo?(mixedpuppy)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: