Stored XSS in chrome://browser/content/browser-media.js line 40

RESOLVED FIXED in Firefox 59

Status

()

defect
RESOLVED FIXED
2 years ago
8 months ago

People

(Reporter: francois.lajeunesse.robert, Assigned: Gijs)

Tracking

({sec-moderate})

57 Branch
Firefox 59
Points:
---
Dependency tree / graph
Bug Flags:
qe-verify -

Firefox Tracking Flags

(firefox-esr52 wontfix, firefox57 wontfix, firefox58 wontfix, firefox59 fixed)

Details

(Whiteboard: [post-critsmash-triage][adv-main59+])

Attachments

(1 attachment, 1 obsolete attachment)

6.07 KB, patch
florian
: review+
Details | Diff | Splinter Review
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0
Build ID: 20180103231032

Steps to reproduce:

Like for BUG 1430511, setting the value of the app.support.baseURL preference to include HTML content will cause an XSS issue in line 40 of chrome://browser/content/browser-media.js.


Actual results:

Whenever a EME video player plugins sends a cdm-disabled message to Firefox, the HTML content generated at line line 40 of chrome://browser/content/browser-media.js is showned in a notification popup. Therefore, any malicious content in the HTML code be executed including JavaScript (executed has privileged js ?!?!). 

Moreover, since :
1- A cdm-disabled message is triggered whenever a user is trying to play DRM while CDM plugging is disabled ;
2- Disabling CDM can be performed by writing to the prefs.js (user_pref("media.gmp-widevinecdm.enabled", false);) 

The malicious content execution could be triggered simply visiting a Web site offfering DRM content.


Expected results:

The value of app.support.baseURL should be sanatized prior to be inserted in HTML content at line 40 of chrome://browser/content/browser-media.js.
Component: Untriaged → General
Assignee: nobody → gijskruitbosch+bugs
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Thank you for finding and reporting those bugs, FLR.
This one looks like a sec-moderate (similar to bug 1430511), as it's limited to local attackers.
But do keep looking, I'm sure you'll get there ;)
Keywords: sec-moderate
Posted patch Patch v0.1 (obsolete) — Splinter Review
Attachment #8943192 - Attachment description: , → Patch v0.1
Attachment #8943192 - Flags: review?(florian)
Comment on attachment 8943192 [details] [diff] [review]
Patch v0.1

Review of attachment 8943192 [details] [diff] [review]:
-----------------------------------------------------------------

This (especially the getFormattedString partial reimplementation in JS) seems to add more complexity than this is worth.

::: browser/base/content/browser-media.js
@@ +35,5 @@
>      return true;
>    },
>    getLearnMoreLink(msgId) {
>      let text = gNavigatorBundle.getString("emeNotifications." + msgId + ".learnMoreLabel");
>      let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");

I'm not sure I understand the threat correctly here, but could we just do something like:
try {
  if (!Services.io.newURI(baseURL).schemeIs("https"))
    throw;
} catch(e) {
  Cu.reportError("invalid app.support.baseURL");
  return "";
}

Should we check that the url is valid directly inside Services.urlFormatter? It seems we have plenty of callers, and some may also insert the url directly into the DOM without sanitization.

@@ +110,5 @@
> +    let message = gNavigatorBundle.getString(msgId);
> +    if (!labelParams.length) {
> +      message = [message];
> +    } else {
> +      message = message.split(/%(?:\$\d)?S/);

Splitting on eg. %$2S without recording the position (2) means we may be swapping the order of the variables if some localized strings don't display the variables in the same order as the en-US string.
Attachment #8943192 - Flags: review?(florian) → review-
(In reply to Florian Quèze [:florian] from comment #3)
> Comment on attachment 8943192 [details] [diff] [review]
> Patch v0.1
> 
> Review of attachment 8943192 [details] [diff] [review]:
> -----------------------------------------------------------------
> 
> This (especially the getFormattedString partial reimplementation in JS)
> seems to add more complexity than this is worth.

I disagree. The complexity will go away with fluent anyway, but we should kill the innerHTML stuff with fire.

> ::: browser/base/content/browser-media.js
> @@ +35,5 @@
> >      return true;
> >    },
> >    getLearnMoreLink(msgId) {
> >      let text = gNavigatorBundle.getString("emeNotifications." + msgId + ".learnMoreLabel");
> >      let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
> 
> I'm not sure I understand the threat correctly here, but could we just do
> something like:
> try {
>   if (!Services.io.newURI(baseURL).schemeIs("https"))
>     throw;
> } catch(e) {
>   Cu.reportError("invalid app.support.baseURL");
>   return "";
> }

We could, but I just really don't think we should be assigning to innerHTML, full stop. app.support.baseURL is only one of many issues.

> Should we check that the url is valid directly inside Services.urlFormatter?
> It seems we have plenty of callers, and some may also insert the url
> directly into the DOM without sanitization.

I guess we could. I'm nervous about breaking the entirety of the prefs when l10n or whatever mis-specifies the URL. Right now, the link would be broken but there'd be no other ill effects.

> @@ +110,5 @@
> > +    let message = gNavigatorBundle.getString(msgId);
> > +    if (!labelParams.length) {
> > +      message = [message];
> > +    } else {
> > +      message = message.split(/%(?:\$\d)?S/);
> 
> Splitting on eg. %$2S without recording the position (2) means we may be
> swapping the order of the variables if some localized strings don't display
> the variables in the same order as the en-US string.

That would be an issue if there was ever more than 1 replacement item, and there isn't.
Flags: needinfo?(florian)
Posted patch Patch v0.2Splinter Review
Attachment #8943192 - Attachment is obsolete: true
Comment on attachment 8943292 [details] [diff] [review]
Patch v0.2

>diff --git a/browser/base/content/browser-media.js b/browser/base/content/browser-media.js
>       case "cdm-insufficient-version":
>         notificationId = "drmContentCDMInsufficientVersion";
>-        params = [this._brandShortName];
>+        notificationMessage = this.getMessageWithBrandName(notificationId);
>         break;
> 
>       case "cdm-not-installed":
>         notificationId = "drmContentCDMInstalling";
>-        params = [this._brandShortName];
>+        notificationMessage = this.getMessageWithBrandName(notificationId);
>         break;

FWIW, I tried to look at removing this duplication but couldn't think of a reasonable solution. We could remove these lines and then add:

if (!notificationMessage) {
  notificationMessage = this.getMessageWithBrandName(notificationId);
}

but that would possibly surprise future consumers and actually adds lines, so I decided not to do that.
Attachment #8943292 - Attachment description: , → Patch v0.1
Attachment #8943292 - Flags: review?(florian)
Attachment #8943292 - Attachment description: Patch v0.1 → Patch v0.2
Flags: needinfo?(florian)
Comment on attachment 8943292 [details] [diff] [review]
Patch v0.2

Review of attachment 8943292 [details] [diff] [review]:
-----------------------------------------------------------------

Thanks for updating the patch, looks much better now. :-)
Attachment #8943292 - Flags: review?(florian) → review+
https://hg.mozilla.org/mozilla-central/rev/ea40957614e1dddbe0a486b3ddac7dfa77709512
Status: ASSIGNED → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → Firefox 59
Group: firefox-core-security → core-security-release
Depends on: 1431471
Flags: qe-verify-
Whiteboard: [post-critsmash-triage]
Whiteboard: [post-critsmash-triage] → [post-critsmash-triage][adv-main59+]
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.