whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.11k stars 2.67k forks source link

Restricting cross-origin WindowProxy access (Cross-Origin-Opener-Policy) #3740

Closed rniwa closed 4 years ago

rniwa commented 6 years ago

Proposal

Add HTTP header called Cross-Origin-Window-Policy, which takes a value of Deny, Allow, and Allow-PostMessage.

When the HTTP response of a document has a Cross-Origin-Window-Policy header, and the value case-insensitively matches Deny ignoring cases, the document is said to be fully isolated. If the value case-insensitively matches Allow-PostMessage ignoring cases, the document is said to be isolated with messaging. If the value doesn't match either or isn't set, then the document is said to be not isolated. If a document is fully isolated or isolated with messaging", it is said to be isolated*.

In a fully isolated document, access to any property on the window proxy of a cross-origin document (regardless of whether the target document is fully isolated or not) results in a SecurityError. In a document isolated with messaging, access to any property except postMessage on the window proxy of a cross-origin document results in a SecurityError. The restriction between two documents are symmetrical and the most stricter of the two prevails.

Furthermore, a new step is inserted into the concept of allowed to navigate before step 1: If B and/or A is isolated and A and B are not of the same origin, return false.

Examples

Let document A and document B be distinct documents its own browsing contexts. If A and B are of the same origin, the header has no effect. If A and B are cross-origin, then:

  1. If document B is fully isolated and document A is not isolated. Any attempt to access a property on document B's window from document A results in a SecurityError. Any attempt to access a property on document A's window from document B also results in aSecurityError`.

  2. If document B is isolated with messaging and document A is not isolated. Any attempt to access a property except postMessage on document B's window from document A results in a SecurityError. Any attempt to access a property except postMessage on document A's window from document B results in a SecurityError.

  3. If document B is isolated with messaging and document A is fully isolated. Any attempt to access a property on document B's window from document A results in a SecurityError. Any attempt to access a property on document A's window from document B results in a SecurityError.

  4. If document B is isolated with messaging and document A is isolated with messaging. Any attempt to access a property except postMessage on document B's window from document A results in a SecurityError. Any attempt to access a property except postMessage on document A's window from document B results in a SecurityError.

Spectre Protection Plan

For the purpose of protecting a website a.com from Spectre in browsers which support process swap for top-level navigations without frame-level process isolation, a.com can set this header on all of its documents (not setting on some would result in leaks; more on this later).

If this header is set on a.com, we can swap process on cross-origin navigation from or to a.com's documents because this header guarantees that a.com doesn't have access to any other document outside of its origin, and vice versa.

Let's say we're on some page B1 in b.com, and it window.open'ed (isolated) a.com. Then b.com doesn't have access to a.com, and b.com doesn't have access to a.com so we can put them into two different processes. Obviously, a.com's iframes don't have access to b.com's frame tree either so if a website is currently relying on being able to do this, they won't be able to use this header.

Let's say now a.com is navigated to some other page B2 in b.com. In this case, the browser finds the process which loaded B1 and load B2 in the same process so that they can talk to one another via window proxies.

csreis commented 6 years ago

@annevk: Thanks for elaborating! And will it be permitted to ignore Cross-Origin-Frame-Policy if the browser can put such frames into a different process? Just want to make sure the spec isn't requiring that the navigation fails if it can be done safely.

annevk commented 6 years ago

@csreis I'm not sure if that makes sense, since you really need both that and Upgrade-No-CORS. Enforcing Upgrade-No-CORS on cross-origin documents seems problematic in a way.

csreis commented 6 years ago

I agree with Upgrade-No-CORS being problematic for cross-origin iframes-- that could possibly be ignored in cross-process subframes as well, or we could look for a different way to approach this restriction.

Mainly, I think we need a way to avoid unnecessary long-term restrictions on pages that request high resolution timers. I can see how Upgrade-No-CORS is useful for those documents and any descendants in the same process (even in the long run), but it stops being necessary for cross-origin subframes if/when browsers can load them in a different process.

Can we design this in a way that allows for it to gracefully upgrade, allowing cross-origin subframes to load in a different process without the CORS restriction?

annevk commented 6 years ago

Yeah sorry, my response needed more nuance, Upgrade-No-CORS would only need to "inherit" within the agent cluster (which by definition is same-site restricted). The main subtlety where you'd need both I think is if you want same-origin and not same-site. Since cross-origin, but same-site, will be in the same agent cluster and thus vulnerable in a way. (We could also chose not to offer same-origin protection for now I suppose.)

csreis commented 5 years ago

Sorry for the delayed reply. I'm not sure if that answered my main question, which is whether we will allow cross-origin iframes in pages that want precise time, if the browser can support cross-process frames.

Two possible ways we might do that:

1) Cross-Origin-Frame-Policy always prevents cross-origin iframes (and thus the Upgrade-No-CORS issue is moot). However, browsers with cross-process frame support can choose to grant access to SharedArrayBuffers, etc, without requiring Cross-Origin-Frame-Policy. Sites can choose whether to include the Cross-Origin-Frame-Policy header based on User Agent. (This is arguably not great, because it requires sites to know which browsers will give them SharedArrayBuffers with and without Cross-Origin-Frame-Policy.)

2) Cross-Origin-Frame-Policy itself can be ignored by browsers with cross-process frames, and Upgrade-No-CORS only applies within the agent cluster. Sites can then serve the Cross-Origin-Frame-Policy header and put cross-origin frames in their page, with the understanding that browsers will display those frames only if it can be done safely (i.e., in a different process). This means the Cross-Origin-Frame-Policy header may lose its meaning over time if more browsers get support for cross-process frames, but so far I haven't heard a strong reason to keep it after cross-process frames are possible.

Would we be ok with the latter?

rniwa commented 5 years ago

I don't think (2) makes sense. Preventing descendent frames in your page to navigate to an URL outside of your own origin/site is a useful feature on its own. What we can do is that once all major browsers have supported per-frame process isolation, then we can enable high precision timers & shared array buffers without these headers.

I don't think we can simultaneously allow some UAs to start exposing high precision timers, and at the same time no cross-UA behavior difference since those two things are fundamentally at odds. Fundamentally, UAs that support per-frame process isolation would allow high precision timers in contexts where other UAs won't, or all UAs are stuck with not enabling high precision timers until all UAs support per-frame process isolation if we wanted the interoperable behaviors across UAs.

Either way, I don't think some UAs ignoring Cross-Origin-Frame-Policy would make much sense because that's no worse than (2) in terms of interoperability and eliminates the benefit of this header.

annevk commented 5 years ago

Note that I think we should always require Upgrade-No-CORS for high resolution timers of any kind (including SharedArrayBuffer). Otherwise attacking resources (that fall outside the CORB safelist) with a high-bandwidth channel is just too easy.

annevk commented 5 years ago
  1. I'd like to start working on specifying this. I'd love help, so please do let this issue know if you're up for doing something. In particular, I hope implementers can help with web-platform-tests, but actual specification work would be great too.
  2. I proposed a v2 feature in #4175 that I'm now convinced is somewhat important for adoption.
arturjanc commented 5 years ago

Given what @annevk said above, I think we have rough agreement about the behavior we want from the L1 and L2 mechanisms, so perhaps it's a good time to have a pie fight about the spelling? ;-) Here are some comments, using https://github.com/whatwg/html/issues/3740#issuecomment-433945551 as the baseline proposal.

My main thought is that for L2 it might make sense to combine Upgrade-No-Cors and Cross-Origin-Frame-Policy into a single header. Given that in https://github.com/whatwg/html/issues/4175 we're considering CORS as the mechanism that would let servers consent to iframing by an L2-isolated document, both of the headers boil down to requiring CORS for cross-origin loads (subresources and frames, respectively). Given that we need both restrictions to enable high-res timers, it might be confusing to expose two separate switches where one suffices:

  1. A side benefit is that even browsers with OOPIFs would need to enforce the CORS requirement on subresources so they will need to adopt the header, reducing the likelihood of having two features with uneven browser support. (Browsers with OOPIFs could theoretically drop the CORS requirement on frame loads, but it wouldn't save them much work, and the developer-facing configuration would be identical: the same header would be required for L2 in all browsers).
  2. From a developer's point of view, I'm also not entirely convinced about the value of specifying sameness in this context. It's important for CORP, but since CORS doesn't have special provisions for same-site loads, I'm not sure we'd gain much from it. Rather, I would expect that developers who build applications which load same-site resources could just set the necessary CORS headers. This also seems like a more secure approach which protects non-cooperating same-site resources (e.g. in the event of an XSS in an L2-enabled application.)
  3. Essentially, I'd expect something like Require-CORS: true which enforces the use of CORS for non-same-origin subresources and frame loads and propagates through descendant iframes to be a fairly straightforward switch that would be sufficient for most applications.

For the Cross-Origin-Opener-Policy proposal, my main comment is along the lines of (2) above -- sameness seems a little more useful in this context, but I'm still not sure if this is a switch that developers really need. Similar to Ryosuke's proposal above and Nika's comment, I'd find COOP: deny-incoming | deny-outgoing | deny-all to be fairly straightforward (though I agree with Anne that it could be a blocker in some scenarios; my hope is that it's more of a niche use case, but I could certainly be convinced otherwise.)

In this world:

Would something like this make sense? (Apologies in advance if I omitted any important considerations from the discussions above.)

annevk commented 5 years ago

I prefer the allow-outgoing design in that you have to be explicit in what to allow. It's also not clear to me that deny-outgoing on its own is something we want or discussed.

It also seems that if we can do CORS preflights for navigations, we can do those for popup navigations too and should perhaps allow allow-outgoing in L2, provided that mechanism is in use.

rniwa commented 5 years ago

Today's Hangout summary:

https://github.com/whatwg/html/issues/3740#issuecomment-433945551 is the current consensus with a clarification that navigation to and from a document with Cross-Origin-Opener-Policy would always result in a new browsing context group. allow-outgoing would only apply to navigations within auxiliary browsing contexts it opened. We'd leave an informal note saying that the author should be deploying CSP navigate-to directive when using allow-outgoing.

annevk commented 5 years ago

We also agreed on renaming allow-outgoing to unsafe-allow-outgoing to signify the risk of letting others have a reference to you (and be in your process in some implementations).

annevk commented 5 years ago

Cases that I don't think we explicitly discussed:

arturjanc commented 5 years ago

I don't have strong feelings either way, so my guess is that we should aim for conceptually the simplest behavior. To me, it would make sense if the header was always ignored in a nested browsing context (so B would have the same behavior regardless of whether it sets the header). When B opens a pop-up, it seems reasonable for it to obey the unsafe-allow-outgoing value set on A -- this introduces a minor leak (A can know if B opened a new window), but this doesn't seem much worse than the status quo. Just my two cents, though...

One other thing that came up earlier but that AFAIK we haven't fully resolved is the Upgrade-No-CORS header and whether to automatically supply credentials on upgraded requests. I think we'd need to do it to not require significant refactoring of code/markup which loads authenticated subresources; but this behavior could be a little surprising, so we should probably put some thought into this.

annevk commented 5 years ago

Okay, so tentatively for Cross-Origin-Opener-Policy:

  1. Only works when declared on a resource navigated to in a top-level browsing context (this includes auxiliary which will become non-auxiliary due to this header except if there's a match).
  2. Sets a policy for the browsing context group (and therefore "inherits" into nested and any auxiliary browsing contexts).
  3. A match between a browsing context group and a new top-level browsing context resource that is navigated to therein requires an identical sameness and unsafe-allow-outgoing. A non-match results in a new browsing context group (within which a new top-level browsing context is created for the non-matching resource).

Let's discuss CORS in #4175. Mozilla still intends to ship both together, but given the reservations stated in the meeting it might be good to somewhat separate the discussions for now.

annevk commented 5 years ago

(I didn't realize this originally covered CORS as well, my bad. I think it would make sense to keep this for Cross-Origin-Opener-Policy and the other issue for enabling SharedArrayBuffer.)

annevk commented 5 years ago

Having written up a more formal description for COOP I realize I made some minor errors above. In particular there's no need for this policy to be on the browsing context group. Either auxiliary browsing contexts are allowed (if the unsafe flag is set) or they create new browsing context groups. So we only need changes to navigating a top-level browsing context (includes auxiliary, to be clear) and creating an auxiliary browsing context (might have to create a new browsing context group instead). Hope this new description aides in review.

annevk commented 5 years ago

I updated the description for COOP linked above with initial about:blank document handling and redirect handling. I think from a HTML Standard perspective we could also make "fast back" work by allowing browsing contexts to be closed/opened in addition to being discarded. We'd close it when replacing it with BCG and open it when going back. I'm not sure if this is worth specifying for v1 (although maybe it turns out to be easier), but as "fast back" is optional anyway I don't think it matters much.

The way the about:blank handling for unsafe-allow-outgoing should work out such that a navigation from initial about:blank ends up with replacement if there's a non-matching COOP on the response. I.e., if there's no COOP on the response, no replacement happens and you get an unsafe reference. If it's a "sameness" navigation and the response has a matching COOP, no replacement happens and you get a reference as well, because about:blank inherits COOP from its creator. A further navigation from that sameness response with COOP to a non-matching COOP would yield replacement. That seems like the right model since otherwise setting the header would sometimes not give the isolating effect it promises.

annevk commented 5 years ago

While working on #4284 I realized that non-auxiliary top-level browsing contexts can also get assigned external state:

(Apologies for originally posting these observations in the wrong issue. Thanks @csreis for the correction. I've marked the corresponding comments in the other issue as off-topic.)

arturjanc commented 5 years ago

Would the network error for sandbox documents only apply to top-level contexts, both non-auxiliary and auxiliary? If so, this is probably okay as they are indeed a fairly niche case -- otherwise, if we also network error'ed sandbox frames, it could be a larger obstacle to adoption, e.g. if the site has ads.

FWIW there are scenarios where maliciously putting a cross-origin document in the sandbox can be used in attacks (e.g. open a document from victim origin in a sandbox window which doesn't allow modals to prevent an alert() from warning the user about some unexpected condition). So the "easy option" of responding with a network error here might actually be security-positive.

annevk commented 5 years ago

Cross-Origin-Opener-Policy only takes effect in top-level, so yes. For nested contexts you could use X-Frame-Options. If you think further exploring dedicated anti-sandbox features are worth it, let's do that in a separate issue (it seems reasonable to me by the way, I was somewhat surprised we never really considered this before)?

annevk commented 5 years ago

A thing we have not discussed in detail here, though @mystor did mention it in https://github.com/whatwg/html/issues/3740#issuecomment-399194225 is history.

Firefox's current approach is history is copied somewhat into the new browsing context group, so history.back() et al do function to some extent. My personal feeling is that the UX for history should continue to work, but API-wise it should feel as if the user navigated directly to your (COOP) document.

What are Chrome and Safari considering here?

csreis commented 5 years ago

A thing we have not discussed in detail here, though @mystor did mention it in #3740 (comment) is history.

Firefox's current approach is history is copied somewhat into the new browsing context group, so history.back() et al do function to some extent. My personal feeling is that the UX for history should continue to work, but API-wise it should feel as if the user navigated directly to your (COOP) document.

What are Chrome and Safari considering here?

For Chrome, we're keeping the session history intact across browsing context groups, both for the back/forward buttons and history.back() et al. Chrome manages the session history for a tab in the browser (parent) process, with a notion of browsing context group (BrowsingInstance) and process (SiteInstance) associated with each session history item.

arturjanc commented 5 years ago

I think the desired behavior here is what @annevk described in https://github.com/whatwg/html/issues/3740#issuecomment-486170969: we want history navigations via the browser UI to work, but ideally they wouldn't be accessible via window.history.

A risk with allowing access to history from JS is that upon a navigation from a site with COOP to an attacker's document the browser may leak interesting information (e.g. an attacker can get information about the number of navigations on the victim site via window.history.length).

If it adds significant implementation / spec difficulty then the severity of the leak is likely not worth addressing, but there are some benefits to removing JS access to history in this case.

annevk commented 4 years ago

As a result of some related issues, in particular how inheritance ought to work and whether unsafe-inherit would be sufficient if an OAuth-chain had cross-site documents in it as well, we ended up simplifying Cross-Origin-Opener-Policy a bit. It now has an explicit default, unsafe-none, which is mostly there to potentially allow for changing the default at a future point (and allow sites to opt out of such a change). And the remaining values are same-origin and same-origin-allow-popups. Given the problems with unsafe-inherit there seemed to be less of a need for same-site-like policies, but adding those back in would not be a lot of work if developers request them.

https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e is still the canonical document and I've updated it with these changes.

JoeMarkov commented 3 years ago

The Cross-Origin-Opener-Policy header seems to be quite similar to what the rel="noopener noreferrer" attribute does when opening a new document in a new tab (target="_blank").

When should I use which one? It seems the COOP header is applicable when I link between origins while the rel="noopener noreferrer" attribute (on anchor tags) seems to work on the same origin as well?

Should I use both? They seem to be quite complimentary.... Some advice here would be nice.