Closed Bug 1970490 (CVE-2025-10529) Opened 1 year ago Closed 1 year ago

cross-origin mask-image is accessible when it shouldn't be, when referenced via a cross-origin trusted stylesheet, which itself is referenced by another origin that's trusted by the stylesheet's origin (but not trusted by the image's origin)

Categories

(Core :: Layout, defect)

defect

Tracking

()

VERIFIED FIXED
143 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr128 --- wontfix
firefox-esr140 143+ verified
firefox139 --- wontfix
firefox140 --- wontfix
firefox141 --- wontfix
firefox142 --- wontfix
firefox143 + verified

People

(Reporter: dholbert, Assigned: dholbert)

References

(Blocks 2 open bugs, Regression)

Details

(Keywords: csectype-sop, regression, sec-moderate, Whiteboard: [adv-main143+][adv-esr140.3+])

Attachments

(5 files)

(This is a variant of bug 1970468 -- that bug is a case where we're being stricter than other browsers, whereas this bug is about a case where we're being more permissive. I think these are two sides of the same coin -- but given that we're more permissive in the version described here, and this in the area of cross-origin security, I'm filing this as security-sensitive, as a precaution.)

tl;dr, consider 3 resources/origins:
A) a web page served from a particular origin, which references...
B) a CSS stylesheet at a different origin, which uses mask-image to reference...
C) an image from yet-another-origin.

For the stylesheet to be usable, it needs to be served with Access-Control-Allow-Origin: [origin A]

For the mask-image to work, the image file also needs to be served with Access-Control-Allow-Origin: ..., but Firefox and Chrome/Safari differ on what ... should be.

  • If the image is served with Access-Control-Allow-Origin: [origin A], then it's usable as a mask-image in Chrome and Safari (but not Firefox). (This is what bug 1969852 is about).
  • If the image is served with Access-Control-Allow-Origin: [origin B], then it's usable as a mask-image in Firefox (but not Chrome and Safari). (This is what I'm filing this bug here to observe/potentially-discuss.) This feels a little broken. Origin C perhaps trusts origin B, but doesn't necessary trust origin A. While origin C allows its image to be used as a mask-image for pages served on origin B, that doesn't necessarily mean that origin C wants its resources to be usable on pages served on other origins that origin B happens to trust.

Probably we'll fix both^ of those issues in the same patch [whether on bug 1970468 or here], but I wanted to have a dedicated place to discuss the possibly security-sensitive aspects here in case it's useful.

Summary: cross-origin mask-image is accessible when it shouldn't be, when referenced via a cross-origin stylesheet which is `Access-Control-Allow-Origin` from site and image request → cross-origin mask-image is accessible when it shouldn't be, when referenced via a cross-origin trusted stylesheet, which itself is referendced by another origin that the stylesheet trusts
Attached file testcase 1 (zipped up)

Here's a testcase (as a zipped up folder) that's near-identical to the one in bug 1970468, except in this one, the image allows itself to be referenced from the stylesheet's origin, rather than from the main website's origin.

STR here are identical to bug 1970468 comment 0 (but with this testcase .zip file instead of the one over there); and the expected/actual results are the opposite of those in bug 1970468 comment 0.

STR:

  1. Download and extract the attached .zip file.
  2. Open 3 separate terminal windows, each inside of the directory that you just extracted from the .zip file.
  3. Run the following 3 commands in those 3 terminals, to spawn 3 distinct web servers (for origins (A), (B), and (C) from bug 1969852 comment 7):
python3 -m http.server 8000
python3 ./serve-stylesheet.py
python3 ./serve-image.py
  1. In a browser, visit http://localhost:8000/test.html

EXPECTED RESULTS:
Nothing at all below the "View me..." line.

ACTUAL RESULTS:
Below the informative "View me..." line, there's a lime-green circle with "Text clipped to a circle" repeated inside of it (and clipped)

Here's a screencast like the one in bug 1970468, showing the results when run with the testcase on this bug here.

As you can see at the end of the screencast, Firefox is the permissive one in this case, allowing the mask-image to be used, while Safari and Chrome do not (and we're probably in the wrong).

mozregression says this is a regression from bug 1496505.

History:

  • Before bug 1418470 (i.e Nightly 2018-09-21 and earlier): we didn't bother checking CORS at all and showed ACTUAL RESULTS (green circle filled with clipped text) here.

  • After bug 1418470 (i.e. Nightly 2018-09-22 through 2018-10-30): we did properly check CORS and block the image load, giving EXPECTED RESULTS (testcase mostly blank, no circle).

  • After bug 1496505 (i.e. Nightly 2018-10-31 and later), we switch to checking a different origin when evaluating CORS here, and we allow the image load, giving ACTUAL RESULTS (green circle filled with clipped text).

Keywords: regression
Regressed by: 1496505

Bug 1970468 has the same regression range. I'll add a note over there.

(I think that confirms that it'll make sense to land one fix (not sure whether on bug 1970468 or here) which will address both bugs; but we'll presumably need to reconsider the case that we were addressing in the regressor, to see if that needs special handling.)

Summary: cross-origin mask-image is accessible when it shouldn't be, when referenced via a cross-origin trusted stylesheet, which itself is referendced by another origin that the stylesheet trusts → cross-origin mask-image is accessible when it shouldn't be, when referenced via a cross-origin trusted stylesheet, which itself is referenced by another origin that's trusted by the stylesheet's origin (but not trusted by the image's origin)

:bradwerth, since you are the author of the regressor, bug 1496505, could you take a look?

For more information, please visit BugBot documentation.

Flags: needinfo?(bwerth)
Flags: needinfo?(bwerth)
Attached file (secure)

This is essentially a backout of bug 1496505, putting its change behind a new
off-by-default about:config pref[1] for now, in case there are use cases that
require it.

[1] content.cors.use_triggering_principal

Assignee: nobody → dholbert
Status: NEW → ASSIGNED

+CC :zombie per bug 1496505 comment 9 in case he has concerns about backing out here, or further thoughts on whether/why extensions might still need our current approach rather than being able to use web_accessible_resources to expose resources for their injected elements to make use of.

(See more thoughts on this in an inline comment on the phabricator page: https://phabricator.services.mozilla.com/D252611#8726869 )

Set release status flags based on info from the regressing bug 1496505

Set release status flags based on info from the regressing bug 1496505

Attachment #9492865 - Attachment description: Bug 1970490: Use loading principal (rather than triggering principal) for CORS checks, by default. r?emilio,smaug → (secure)
Attached file (secure)
Blocks: 1970468
Pushed by dholbert@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/cb5435011e89 https://hg.mozilla.org/integration/autoland/rev/e7d60456dd06 Use loading principal (rather than triggering principal) for CORS checks, by default. r=smaug

This seems to have caused a CORS xpcshell test to start failing, e.g.
https://treeherder.mozilla.org/logviewer?job_id=522001207&repo=autoland

01:07:41 WARNING - TEST-UNEXPECTED-FAIL | netwerk/test/unit/test_cors_preflight_dns_cache.js | xpcshell return code: 0
01:07:41 WARNING - TEST-UNEXPECTED-FAIL | netwerk/test/unit/test_cors_preflight_dns_cache.js | test_cors_with_valid_dns_cache - [test_cors_with_valid_dns_cache : 137] 0 == 1

I requested a backout and I'll take a look before relanding.

Pushed by sstanca@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/aaf716458633 https://hg.mozilla.org/integration/autoland/rev/876b81435758 Revert "Bug 1970490: Use loading principal (rather than triggering principal) for CORS checks, by default. r=smaug" for causing xpcshell failures in test_cors_preflight_dns_cache.js.

(In reply to Daniel Holbert [:dholbert] from comment #12)

This seems to have caused a CORS xpcshell test to start failing, e.g.
https://treeherder.mozilla.org/logviewer?job_id=522001207&repo=autoland

01:07:41 WARNING - TEST-UNEXPECTED-FAIL | netwerk/test/unit/test_cors_preflight_dns_cache.js | xpcshell return code: 0
01:07:41 WARNING - TEST-UNEXPECTED-FAIL | netwerk/test/unit/test_cors_preflight_dns_cache.js | test_cors_with_valid_dns_cache - [test_cors_with_valid_dns_cache : 137] 0 == 1

I requested a backout and I'll take a look before relanding.

So, the test failure there is indicating that the CORS request was rejected due to mismatching principal, I think.

The test explicitly sets up a custom triggeringPrincipal and uses it for some CORS requests, presumably because it expects us to take that triggeringPrincipal into account (as we've been doing incorrectly AFAICT):
https://searchfox.org/mozilla-central/rev/68a6b1a19cc76683c8c96430f091ba7218728bb4/netwerk/test/unit/test_cors_preflight_dns_cache.js#86,88-91,93,96,98-106

function createCORSRequest(corsURI) {
...
  let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
    firstPartyDomain: "https://example.org",
  });
  let channel = NetUtil.newChannel({
...
    loadingPrincipal: principal,
...
  }).QueryInterface(Ci.nsIHttpChannel);
...
  let triggeringPrincipal =
    Services.scriptSecurityManager.createContentPrincipal(
      NetUtil.newURI("https://example.org/"),
      {
        firstPartyDomain: "https://example.org",
      }
    );

  channel.loadInfo.setTriggeringPrincipalForTesting(triggeringPrincipal);

With this bug's patch applied: if I swap the usage of triggeringPrincipal and principal in the code quoted above, then that makes the test start passing again. (That makes sense given that this bug's patch is about whether to use triggeringPrincipal vs. loadingPrincipal for CORS checks.)

I need to grok the test and the bug that added it (bug 1960834) to have a better idea of whether the test as-it-stands is correct, if it'd be correct to modify it in this or a similar way, etc.

[+CC kershaw who worked on the test & the associated bug, if he has insights here too on whether checking triggeringPrincipal might in fact be the correct thing to be doing for this test's CORS requests...]

The reason we set triggeringPrincipal in this test is to intentionally trigger a CORS preflight request. Before the changes in this bug, nsCORSListenerProxy used triggeringPrincipal to decide whether to initiate a preflight, so I added a hacky method in nsILoadInfo.idl to let us override triggeringPrincipal.
For the test, it's not important which principal is used, only that the CORS preflight was triggered.
With your patch, we can remove that triggeringPrincipal hack. However, we may also need to update how we retrieve entries from the preflight cache, since that currently uses loadingPrincipal to store the entry.

Thanks! I poked at the test a bit and I'm not sure the best way to evolve it for the time being (since it seems to be (ab)using triggeringPrincipal to convert what-would-be-same-origin-requests into cross-origin requests, in order to trigger a particular CORS-preflight situation).

Given that my patch here is adding a pref to restore the old behavior, I'm inclined to just toggle the pref for the benefit of this test for the time being, and file a followup to sort it out so that the test doesn't need this pref-flip. (The test's reliance on triggeringPrincipal seems to just be a bit of hack to get into a particular situation, rather than being an important part of the security guarantees of the test itself, so reenabling the triggeringPrincipal behavior to continue to let the test get into that situation seems ~fine for the time being; but also we should fix the test to remove this reliance before too too long, because this pref should probably be slated-for-eventual-removal once it's made it to release and been proven not to cause fallout.)

Blocks: 1982916

(In reply to Daniel Holbert [:dholbert] from comment #16)

Given that my patch here is adding a pref to restore the old behavior, I'm inclined to just toggle the pref for the benefit of this test for the time being, and file a followup to sort it out so that the test doesn't need this pref-flip

Filed bug 1982916.

Pushed by dholbert@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/8d70a22c4ea4 https://hg.mozilla.org/integration/autoland/rev/476f23adddc7 Use loading principal (rather than triggering principal) for CORS checks, by default. r=smaug
Group: layout-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 1 year ago
Resolution: --- → FIXED
Target Milestone: --- → 143 Branch
QA Whiteboard: [qa-triage-done-c144/b143] [qa-ver-needed-c144/b143] [sec]
Flags: qe-verify+
Regressions: 1984003

Given that this patch mitigates one avenue for cross-site data-leakage, I think we'll ultimately want to uplift this to some or all supported ESR branches -- but it'll likely be worth waiting/investigating a bit to see if there are any other add-ons that were inadvertently depending on our permissiveness here, as in bug 1984003.

The cost/benefit of uplifting may be a bit fuzzy here -- as far as I know, the cross-site data-leakage that was made possible by this bug required a particular configuration on the attacked site's side. To be vulnerable to data leakage, I think a site would need something like the setup described in comment 0.

An attack setup would be something like this:

  • Origin A is the attacker, e.g. https://evil.example.com . Let's say it references the stylesheet at Origin B mentioned below.
  • Origin B (part of the attacked site), hosting a stylesheet which has a permissive CORS configuration (e.g. Access-Control-Allow-Origin: *, or explicitly allowing the attacker's Origin A for whatever reason). This stylesheet would need to reference a CSS mask-image (for example) from Origin C.
  • Origin C (part of the attacked site), hosting some resources that are sensitive and not meant to be readable by attacker pages. These are unconditionally served with Access-Control-Allow-Origin: [Origin B].

In this setup, all browsers will apply the stylesheet from Origin B to the attacker site at Origin A, and will make a CORS request to get the resource at Origin C, with Origin: [Origin A] and with triggeringPrincipal set to Origin B. The response will come back with Access-Control-Allow-Origin: [Origin B]. Firefox versions without the patch will mistakenly interpret this as a case where the resource is allowed to be used, and will render it as part of Origin A, which potentially lets Origin A learn the content/geometry of the resource by using timing attacks.

Anyway - given the particular requirements (origin B & C with particular CORS setups) that seem to be required on a vulnerable-site for a successful attack here, it's possible that this is not a realistic attack vector, in which case maybe (?) the cost/benefit of uplifting might be debatable. We can hash that out over coming weeks, but for now I'm on the lookout for regressions.

Blocks: 1985652
No longer blocks: 1985652
Regressions: 1985652
Attached file (secure)

This is essentially a backout of bug 1496505, putting its change behind a new
off-by-default about:config pref[1] for now, in case there are use cases that
require it.

[1] content.cors.use_triggering_principal

Original Revision: https://phabricator.services.mozilla.com/D252611

Attachment #9511217 - Flags: approval-mozilla-esr140?

firefox-esr140 Uplift Approval Request

  • User impact if declined: Potential for cross-site data leakage, due to mixed up logic when valildating CORS permissions.
  • Code covered by automated testing: no
  • Fix verified in Nightly: yes
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: See STR in https://bugzilla.mozilla.org/show_bug.cgi?id=1970490#c1
  • Risk associated with taking this patch: low
  • Explanation of risk level: The risk here is from breakage where something inadvertently depends on permissiveness. For regular web content, that shouldn't be the case because this patch is bringing us into alignment with other browsers. For WebExtensions, there are known cases where WebExtensions depend on our permissiveness, but followup bug 1970490 (which should be uplifted alongside this) addresses that by preserving our permissive behavior specifically for WebExtensions.
  • String changes made/needed: None
  • Is Android affected?: yes

(In reply to Phabricator Automation from comment #22)

  • Explanation of risk level: The risk here is from breakage where something inadvertently depends on permissiveness. For regular web content, that shouldn't be the case because this patch is bringing us into alignment with other browsers. For WebExtensions, there are known cases where WebExtensions depend on our permissiveness, but followup bug 1970490 (which should be uplifted alongside this) addresses that by preserving our permissive behavior specifically for WebExtensions.

Sorry, my typo there -- I meant "followup bug 1984003". We need to uplift that bug's patch along with this bug's patch in order to preserve our historical permissiveness for extensions (to avoid breaking them).

(or rather: we need to uplift followup bug 1985652, which is what bug 1984003 is duped to)

firefox-esr140 Uplift Approval Request

  • User impact if declined: Potential for cross-site data leakage, due to mixed up logic when valildating CORS permissions.
  • Code covered by automated testing: no
  • Fix verified in Nightly: yes
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: See STR in https://bugzilla.mozilla.org/show_bug.cgi?id=1970490#c1
  • Risk associated with taking this patch: low
  • Explanation of risk level: The risk here is from breakage where something inadvertently depends on permissiveness. For regular web content, that shouldn't be the case because this patch is bringing us into alignment with other browsers. For WebExtensions, there are known cases where WebExtensions depend on our permissiveness, but followup bug 1970490 (which should be uplifted alongside this) addresses that by preserving our permissive behavior specifically for WebExtensions.
  • String changes made/needed: None
  • Is Android affected?: yes
Attachment #9511217 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+
Whiteboard: [adv-main143+]
Whiteboard: [adv-main143+] → [adv-main143+][adv-main140.3+]
Whiteboard: [adv-main143+][adv-main140.3+] → [adv-main143+][adv-esr140.3+]
Alias: CVE-2025-10529

Reproduced the initial issue using an old Nightly build from 2025-06-04. Verified that using 140.3.0esr and 143.0rc builds across platforms (Windows 11, macOS 13 and Ubuntu 22.04) this is not reproducible anymore, it respects the expected behavior from comment 1.

Status: RESOLVED → VERIFIED
QA Whiteboard: [qa-triage-done-c144/b143] [qa-ver-needed-c144/b143] [sec] → [qa-triage-done-c144/b143] [qa-ver-done-c144/b143] [sec]
Flags: qe-verify+

I think we're good to land the automated test here; all branches where we're planning to fix this have now had the fix for at least one release. (The last one to get the fix was ESR140 which got patched in esr140.3, and we've released esr140.4 since then.)

I'm going to run the test through Try to be sure it's not flaky (up until now I'd just been testing locally).

Try looks good: https://treeherder.mozilla.org/jobs?repo=try&revision=f974918942848a4256cb137ae41590c5588f698e

(It caught one trailing-whitespace linting issue, which I'll fix; but no actual test failures.)

So: given that & comment 28, I'll land the test here.

Pushed by dholbert@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/9b47aad215d5 https://hg.mozilla.org/integration/autoland/rev/bf82e6f2ba9b Add WPT for cross-origin mask-image referenced from cross-origin stylesheet. r=emilio,layout-reviewers
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: