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)
Tracking
()
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.
| Assignee | ||
Updated•1 year ago
|
| Assignee | ||
Comment 1•1 year ago
|
||
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:
- Download and extract the attached .zip file.
- Open 3 separate terminal windows, each inside of the directory that you just extracted from the .zip file.
- 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
- 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)
| Assignee | ||
Comment 2•1 year ago
|
||
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).
| Assignee | ||
Comment 3•1 year ago
|
||
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).
| Assignee | ||
Updated•1 year ago
|
| Assignee | ||
Comment 4•1 year ago
•
|
||
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.)
Updated•1 year ago
|
| Assignee | ||
Updated•1 year ago
|
Comment 5•1 year ago
|
||
:bradwerth, since you are the author of the regressor, bug 1496505, could you take a look?
For more information, please visit BugBot documentation.
Updated•1 year ago
|
| Assignee | ||
Comment 6•1 year ago
|
||
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
Updated•1 year ago
|
| Assignee | ||
Comment 7•1 year ago
•
|
||
+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 )
Comment 8•1 year ago
|
||
Set release status flags based on info from the regressing bug 1496505
| Assignee | ||
Updated•1 year ago
|
Updated•1 year ago
|
Comment 9•1 year ago
|
||
Set release status flags based on info from the regressing bug 1496505
Updated•1 year ago
|
Updated•1 year ago
|
| Assignee | ||
Comment 10•1 year ago
|
||
Updated•1 year ago
|
Comment 11•1 year ago
|
||
| Assignee | ||
Comment 12•1 year ago
|
||
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.
Comment 13•1 year ago
|
||
| Assignee | ||
Comment 14•1 year ago
•
|
||
(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=autoland01: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 == 1I 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...]
Comment 15•1 year ago
|
||
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.
| Assignee | ||
Comment 16•1 year ago
•
|
||
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.)
| Assignee | ||
Comment 17•1 year ago
|
||
(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.
Comment 18•1 year ago
|
||
Comment 19•1 year ago
|
||
Updated•1 year ago
|
| Assignee | ||
Comment 20•1 year ago
•
|
||
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 CSSmask-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.
| Assignee | ||
Updated•1 year ago
|
| Assignee | ||
Comment 21•1 year ago
|
||
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
Updated•1 year ago
|
Comment 22•1 year ago
|
||
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
| Assignee | ||
Comment 23•1 year ago
|
||
(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).
| Assignee | ||
Comment 24•1 year ago
•
|
||
(or rather: we need to uplift followup bug 1985652, which is what bug 1984003 is duped to)
Comment 25•1 year ago
|
||
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
Updated•1 year ago
|
Updated•1 year ago
|
Comment 26•1 year ago
|
||
| uplift | ||
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Updated•1 year ago
|
Comment 27•1 year ago
|
||
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.
| Assignee | ||
Comment 28•10 months ago
|
||
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).
| Assignee | ||
Comment 29•10 months ago
|
||
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.
Comment 30•10 months ago
|
||
Comment 31•10 months ago
|
||
Updated•5 months ago
|
Description
•