whatwg / html

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

HTML's "navigate" algorithm should handle 'X-Frame-Options' and CSP's 'frame-ancestors' #1230

Closed mikewest closed 4 years ago

mikewest commented 8 years ago

See https://github.com/whatwg/fetch/issues/302 where I initially filed this request. @annevk's suggestion that we handle the header inside the navigation algorithm (probably as step 21, after the response checks?) SGTM.

This will require some changes to CSP as well to move frame-ancestors from a response check in Fetch to a new check in the navigate algorithm.

annevk commented 8 years ago

This should be pretty easy to add now.

mikewest commented 8 years ago

Poking at this today. For frame-ancestor, I expect to add the relevant hooks to CSP, and, then call them from https://html.spec.whatwg.org/#process-a-navigate-response. For X-F-O, I'll make up something that at least somewhat aligns with RFC7034.

mikewest commented 8 years ago

"Pretty easy to add", he says.

Basically, we didn't think about this enough, and it's more subtle than I though, because we need the source browsing context's policy in some cases (form-action), and the target browsing context's parent's policy in others (frame-src), and the response's policy in still others (frame-ancestors). I think these two hooks give me the tools I'll need to poke at things in CSP, but I wonder if there's a better way...

WDYT?

annevk commented 8 years ago

One way to do form-action is to just hook it directly in form submission. And just navigate to a network error if it blocks. If it's specific to forms invoking navigate we might as well keep it there.

The others remain complicated.

mikewest commented 8 years ago

Hooking directly into form submission wouldn't catch redirects, would it? I think we need to live in navigate for that to work.

annevk commented 8 years ago

Good point.

mikewest commented 8 years ago

Are you waiting on me for this PR? (Sorry, I've been out for almost two weeks, so I'm trying to page this back in....)

annevk commented 8 years ago

Yeah, the review comments are not addressed yet I thought.

domenic commented 6 years ago

Today @travisleithead discovered that Chrome does not support the ALLOW-FROM variant. Edge had strict parsing, which broke when it encountered a web site with ALLOW-FROM=https://example.com/.

If we do manage to get around to speccing this, so that nobody else runs into these sorts of interop issues while building their browsers, we'll need to figure out what to do with ALLOW-FROM. Probably also questions around whitespace trimming etc. So yeah, not just semantic issues, also syntactic ones :(

annevk commented 4 years ago

As mentioned in https://bugzilla.mozilla.org/show_bug.cgi?id=1599256 this should also define what kind of document does get loaded (about:blank?) and whether that creates a load event. (That's what Firefox will implement unless Chrome starts treating this as a network error soon.)

mikewest commented 4 years ago

Last I checked, Chrome (and WebKit) loads an error page with an opaque origin (data:... in WebKit, and chrome://interstitial (I think) in Chrome), and does fire onload when a page is blocked via XFO (note that Chrome has different behavior for CSP's frame-ancestors because that's still handled in our renderer, but it's moving out to the browser in ~80, and aims to have the same behavior as XFO).

annevk commented 4 years ago

Ah, I guess Chrome in general fires the load event for network errors? I.e., I get it both for https://expired.badssl.com/ and https://google.com. Maybe that's reasonable enough.

mikewest commented 4 years ago

Yes, that's my understanding of Chrome's (and WebKit's, I believe) behavior. We move network errors to an opaque-origin'd error page, and fire an onload.

domenic commented 4 years ago

I'm interested in tackling this in the near future.

It looks like currently frame-ancestors is completely handled, right? That is, https://html.spec.whatwg.org/#process-a-navigate-response step 1 handles frame-ancestors (and other CSP checks, like form-action and frame-src). It creates a network error. Network error pages have an opaque origin (clearer after #5736) and fire a load event (per https://html.spec.whatwg.org/#read-ua-inline "stopped parsing" step).

So this bug reduces to only X-Frame-Options handling. There'll be a bit of trickiness around parsing, but otherwise it should be pretty straightforward once parsed. Assuming we don't support ALLOWFROM (tests needed), then we should have all the info we need. Something like:

  1. If an enforce-disposition frame-ancestors is present, then ignore the X-Frame-Options header.
  2. If deny, and browsingContext is a child browsing context, then network error.
  3. If sameorigin, and browsingContext is a child browsing context, then check if finalResponseOrigin is same-origin with browsingContext's container's relevant settings object's top-level origin. If not, network error.

The last step only checking top-level origin (instead of all intermediate origins) is based off of the remarks in https://w3c.github.io/webappsec-csp/#frame-ancestors-and-frame-options about "many user agents"

domenic commented 4 years ago

Hmm per the discussions in https://bugzilla.mozilla.org/show_bug.cgi?id=725490 it looks like Chrome and Firefox implement all-ancestors checking, which is nice. And everyone passes the associated WPTs. https://wpt.fyi/results/x-frame-options?label=master&label=experimental&aligned&q=x-frame-options

domenic commented 4 years ago

So I've got an initial spec for this up in https://github.com/whatwg/html/pull/5737. The question remains on what to do in "conflict" cases like DENY,SAMEORIGIN. Per the description of Chrome's algorithm in https://github.com/web-platform-tests/wpt/pull/21730#issuecomment-585161928, and the test results in https://github.com/web-platform-tests/wpt/pull/21730 and https://github.com/web-platform-tests/wpt/pull/24618, we could expand https://github.com/whatwg/html/pull/5737 with something along the following lines:

  1. Remove all tokens from xFrameOptions that are not ASCII case-insensitive matches for one of "sameorigin", "deny", "allowall".
  2. Remove duplicate tokens from xFrameOptions
  3. If xFrameOptions contains >=2 tokens, then return false (block the response).
  4. If xFrameOptions contains 0 tokens, then return true (do not block the response).
  5. If xFrameOptions[0] is "deny", then return false.
  6. If xFrameOptions[0] is "allowall", then return true.
  7. If xFrameOptions[0] is "sameorigin", then perform the steps in the PR to walk the tree.

(this is meant to be equivalent to the algorithm @mikewest outlines, but with more clarity at the cost of more O(n) operations. We could also just use the Chrome algorithm as-is.)

Note that this conflict cases, according to Chromium metrics back in February, affects 0.002% of page loads.

I'm OK with either the current simple spec proposal in https://github.com/whatwg/html/pull/5737 (which I believe matches Firefox behavior), or with the more complicated Chromium version. I like simplicity, but I am sympathetic to the argument that conflicts are a sign that something has gone wrong and we should err on the side of blocking. Also, blackbox testing indicates that the Chromium model and the Safari model are probably the same, so it might be easier to just align there.

What would Mozilla prefer? /cc @mozfreddyb

EDIT: further testing reveals that the above algorithm proposal doesn't seem quite correct. In particular invalid,sameorigin blocks same-origin framing in Chrome and Safari but the above algorithm would let it through.

domenic commented 4 years ago

Alright. I did another pass through the tests, and rewrote everything to be less copy-pastey, and made sure that every same-origin test has a corresponding cross-origin test. Results over at https://github.com/web-platform-tests/wpt/pull/24618.

Here is the current status of browsers vs. the spec draft in https://github.com/whatwg/html/pull/5737:

I don't have strong feelings about which model is better. The proposed spec is simpler, but it seems like the Chrome and Safari behavior might be a bit more strict, which could be good.

/cc @dveditz

/cc @ArthurSonzogni who I noticed working a bit on XFO in Chromium recently.

domenic commented 4 years ago

Given the lack of engagement from implementers here I've decided the simplest course is to match the spec to 2/3 browsers and get things merged. I'll work on the spec/test updates for that now.