whatwg / html

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

Should a CSP failure during navigation unload the existing page? #2415

Open bzbarsky opened 7 years ago

bzbarsky commented 7 years ago

Consider the testcase at http://web.mit.edu/bzbarsky/www/testcases/security/csp-basic-test.html. The basic structure is a page on some host, with CSP "default-src 'self'", which loads a subframe from the same host, then tries to navigate it to some other host. This navigation should clearly be blocked by CSP, but how should that work in practice?

Per current spec as written, we land in https://html.spec.whatwg.org/multipage/browsers.html#process-a-navigate-fetch which does a CSP check in step 6, which sets response to a network error. I believe this has no "location URL", so we end up all the way in step 13 doing https://html.spec.whatwg.org/multipage/browsers.html#process-a-navigate-response which for network errors always does https://html.spec.whatwg.org/multipage/browsers.html#read-ua-inline which ... is as "should"-level requirement for creating the new document? I'm not sure in what sense it uses "should" there....

Anyway, the upshot is that if the new document is created, the UA is supposed to update the session history with it, which unloads the existing document.

Per my testcase above, Edge unloads; Chrome, Safari, and Firefox do not. We should figure out what the actual desired behavior here is and make browsers agree.

Ideally, whatever the behavior here is it would match the behavior for mixed content, for http: attempting to load file:, for http: attempting to load about:config or similar browser-internal bits, etc, so we have some consistency to all this stuff.

I should note that in Edge the "http: links to file:" case does not unload....

//cc @mikewest @travisleithead @domenic @annevk

domenic commented 7 years ago

/cc @patrickkettner; this is related to https://bugs.chromium.org/p/chromium/issues/detail?id=698559 as per @bzbarsky's above paragraph

Ideally, whatever the behavior here is it would match the behavior for mixed content, for http: attempting to load file:, for http: attempting to load about:config or similar browser-internal bits, etc, so we have some consistency to all this stuff.

bzbarsky commented 7 years ago

ccing some webkit folks too; not sure who the right ones are: @rniwa @cdumez

cdumez commented 7 years ago

I am not the right person for this but I forwarded the request to people who know more about these things. They may comment here if they have an opinion.

mikewest commented 7 years ago

The basic structure is a page on some host, with CSP "default-src 'self'", which loads a subframe from the same host, then tries to navigate it to some other host. This navigation should clearly be blocked by CSP, but how should that work in practice?

Note also that blockage can happen in the other direction; the target of the navigation could serve frame-ancestors 'none', or x-frame-options: DENY. I don't think this changes the desired behavior, but it's good to note that we want to align these mechanisms along with mixed content, file:, etc.

Currently, those checks are handled in "Process a navigate response" (since we don't know about the response headers until that point, so we can't block the navigation a priori), and are specced to have the same effect as a network error.

Anyway, the upshot is that if the new document is created, the UA is supposed to update the session history with it, which unloads the existing document. Per my testcase above, Edge unloads; Chrome, Safari, and Firefox do not. We should figure out what the actual desired behavior here is and make browsers agree.

For X-Frame-Options and frame-ancestors, I believe Chrome navigates to a blank document with an opaque origin (we intend to display an error page, but aren't today because Chrome's error page implementation has some issues in a site-isolation world that we're working towards fixing).

I'd be totally willing to change Chrome's behavior to display an error page for request-time blocks like CSP and MIX as well. In fact, there's some infrastructure work underway to move navigational CSP and MIX checks up to the browser process. If you run Chrome with --enable-browser-side-navigation, I believe you'll actually see the "blank document with an opaque origin" behavior for blocked form submissions after https://codereview.chromium.org/2689653003 lands (so, maybe a canary sometime next week?). That seems like the right direction to move in.

bzbarsky commented 7 years ago

One issue with that approach is that ideally about:some-privileged-thing and about:some-nonexistent-thing would behave identically, so you can't even detect the existence of the privileged thing.

And for about:some-nonexistent-thing per https://bugs.chromium.org/p/chromium/issues/detail?id=698559#c3 there may be web compat reasons to not do the "navigate to an opaque origin error page" behavior...

mikewest commented 7 years ago

One issue with that approach is that ideally about:some-privileged-thing and about:some-nonexistent-thing would behave identically, so you can't even detect the existence of the privileged thing.

I'm not sure I agree. https://same-origin.com/ and https://cross-origin.com/ act differently in detectable ways. Why wouldn't about:blank and about:not-so-blank act differently if the latter maps to something interesting in a particular user agent?

Is that your only qualm with the "navigate to an opaque origin error page" behavior, if we limit the discussion to explicit blocking mechanisms like MIX, CSP, XFO, etc?

And for about:some-nonexistent-thing per https://bugs.chromium.org/p/chromium/issues/detail?id=698559#c3 there may be web compat reasons to not do the "navigate to an opaque origin error page" behavior...

Chrome does some magical mapping of things like about:flags to chrome://flags when it's loaded at the top level. When it's loaded as an iframe, we treat it as about:blank. I'll have to dig around a bit to find the relevant code, as navigation is a bit of a mess right now...

If folks are indeed relying on alternative spellings of blank to mean the same thing as blank, then I agree that it might be difficult to implement the spec's current mandate which limits the path to blank in https://fetch.spec.whatwg.org/#concept-basic-fetch.

Would it be reasonable to hand-wave that section a bit, speccing the about:whatever behavior that Chrome and Firefox have apparently settled on, and leaving wiggle room for the user agent to return something opaque for those URLs that it understands (perhaps only as top-level navigations)?

CCing @johnwilander, who landed https://github.com/WebKit/webkit/commit/90426dd303e878a4e6b433a8852e35a66334e9c9, and could fill us in on his motivations for the change.

bzbarsky commented 7 years ago

https://same-origin.com/ and https://cross-origin.com/ act differently in detectable ways.

Yes, but they both load the thing (modulo X-Frame-Options).

Why wouldn't about:blank and about:not-so-blank act differently

They would, and should, and do. What I'm talking about are about:chrome and about:daisies. If neither one is going to lead to the thing actually being loaded, I would argue they should be consistent in how they don't load the thing as opposed to having two different ways of not loading things.

Is that your only qualm with the "navigate to an opaque origin error page" behavior,

I don't know; I haven't thought this through very well yet. The other obvious qualm is changing longstanding behavior and resulting web compat fallout. "navigate to an opaque origin error page" and "leave about:blank there" are obviously quite different from the point of view of the page, and script written expecting one would totally break if the other happens.

if we limit the discussion to explicit blocking mechanisms like MIX, CSP, XFO, etc

I think it's very confusing if MIX does something different from the "http: can't load file:" policy or the "http: can't load about:chrome" or "http: can't load chrome://flags". There is no principled difference between them, imo. CSP at least can be argued to be different because it's a page opt-in. But again, I'm not sure there's much value in having two different ways of "not loading" things...

speccing the about:whatever behavior that Chrome and Firefox have apparently settled on

From your description it sounds like the behaviors are actually different. A simple testcase confirms this:

data:text/html,<iframe src="http://example.com" name="f"></iframe><a href="about:daisies" target="f">Click me</a>

In Chrome this navigates to about:blank, while in Firefox it is treated like an HTTP 204, effectively.