Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

History traversal and the request's origin. #4446

Closed
mikewest opened this issue Mar 26, 2019 · 16 comments
Closed

History traversal and the request's origin. #4446

mikewest opened this issue Mar 26, 2019 · 16 comments

Comments

@mikewest
Copy link
Member

Step 5.10 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch serializes a request's origin in order to generate an Origin header. It's not clear to me what that's going to be for a navigation generated from history traversal.

Step 1.3 of traversing the history suggests that we ought to perform the navigation using the "same source browsing context as was used the first time [the] entry was created" (which then populates the request's origin by virtue of being assigned as the request's client in step 2 of "process a navigate fetch". I don't think that's technically possible in most cases, as the source browsing context has almost certainly been discarded, or is otherwise unavailable.

Perhaps it would make sense to store some of the information about the source browsing context on the session history entry instead, so that it can be passed into the navigation algorithm in some way that supports doing the right thing for the Origin header (as well as Sec-Fetch-Site and SameSite cookies, which will have similar requirements on the request's data).

/cc @naskooskov, @anforowicz, @csreis

(I'm not exactly sure what #3625 is asking for... this bug might be a more-specific dupe?)

@naskooskov
Copy link

Storing the source browsing context origin on the session history entry and using it in history navigations seems a bit strange to me.

Consider navigation to A first, which navigates to B, which navigates to C. If we store the source browsing context origin, for the navigation to B that would be A. However, if document C does history.back(), then it is not very logical to use A as the origin of the source browsing context, since in reality it is document in C that caused the navigation to happen.

P.S. The origin of the source browsing context is referred to as "initiator origin" in Chromium's vocabulary, so if I slip up and use it instead of the spec version, please excuse it.

@mikewest
Copy link
Member Author

@naskooskov: What would you do for the "User clicked the browser's 'Back!' button" case? There, it seems like we need to move them back to what they were seeing previously, as that's what they're asking for. To do so, we need to recreate the context of the request, don't we?

Also, to turn your example around: let's say that evil.com caused a navigation to example.com/do-a-bad-thing, which is somehow tricked into navigating the user to example.com/call-history.back(), which causes the bad thing to happen because it's seen as same-origin, oh noes!

At core, history seems different to me than other forms of navigation. In either your example or mine, the fact that example.com caused the history traversal does not mean that example.com is responsible for the history entry. The responsibility lies with the original navigation's initiating context, IMO, and that's where it ought to remain for the lifetime of the history entry.

@arturjanc
Copy link

I think my mental model matches @mikewest's here: the "Back" button is more "take me back in time and show me the same pixels I saw" than "perform a navigation to the URL I was on before from the page I'm on now"; I think this also aligns with what bfcache implementations are doing. In that world, using the original origin of the source browsing context for a back navigation doesn't seem unreasonable.

My guess is that this will become more important in a world where applications get more selective about navigations to their sensitive endpoints to protect against various classes of infoleaks, and will want to reject some cross-site requests without breaking legitimate functionality.

@annevk
Copy link
Member

annevk commented Mar 28, 2019

One caveat here is that passing around a browsing context is wrong (you also cannot talk of an origin of a browsing context); see #1130. Source browsing context should probably be a document instead or more likely some kind of struct with the relevant state originally initialized from a document.

@anforowicz
Copy link

I think I agree with the view that the "Back" button means "take me back in time and show me the same pixels I saw" (e.g. this is why browsers prompt and replay POST payload if needed). I also agree that having different results based on whether the page was in bfcache would be unfortunate.

Still, to try to play devil's advocate - there is another security attack to consider:

  1. Attacker includes a link to the attacker-controlled-site on the victim's site (e.g. by posting a link to support forums?)
  2. Attacker tricks the user into clicking the link above
  3. After the user clicks a link
    • the browser will navigate from victim's site to attacker-controlled-page
    • the attacker-controlled-page opens a new, same-origin pop-up window
    • the pop-up window stays around to measure navigation timings and to make it possible to repeat the measurements/navigations many times
    • the pop-up window navigates its opener to another-attacker-controlled-page (by setting window.opener.location)
    • the another-attacker-controlled-page calls window.history.back() to navigate it back to the victim's site/page

If we replay the original Sec-Fetch-Site (and/or SameSite cookies), then it means that (in the last step above) a cross-site attacker could trigger a navigation with Sec-Fetch-Site: none or Sec-Fetch-Site: same-origin (assumming the original navigation to the victim site was done from the address bar or from same origin). If instead window.history.back() was treated as attacker-initiated navigation, then it would get Sec-Fetch-Site: cross-site.

@anforowicz
Copy link

@naskooskov,

Consider navigation to A first, which navigates to B, which navigates to C. If we store the source browsing context origin, for the navigation to B that would be A. However, if document C does history.back(), then it is not very logical to use A as the origin of the source browsing context, since in reality it is document in C that caused the navigation to happen.

Right now in Chromium all history navigations are browser-initiated, so the origin (for calculating Sec-Fetch-Site, SameSite cookies, maybe the Origin header) would not be present (and would not be C, even though as you say it is C that caused the navigation to happen).

@arturjanc
Copy link

Attacks based on sanitized HTML on the victim site (e.g. attacker-controlled links, etc.) are fairly interesting in this context and will probably require developers who implement navigation restrictions to do some work to prevent bypassing such restrictions.

For example, if an attacker has the ability to post a link on the victim site, she can already navigate to arbitrary endpoints in the victim origin as site=same-origin, which makes any control of navigations moot. I expect that sanitizers would have to set something like rel="noopener" (e.g. rel="noorigin") or rewrite user-supplied URLs to redirect through a cross-origin endpoint to make navigations to these URLs appear as untrusted.

I think the class of attacks you described is definitely worth considering, though I'm not entirely sure how risky it is to allow the attacker to re-navigate the user to a URL on which the user had legitimately landed before (it seems similar to the user refreshing the page). Especially in a world with COOP, if the application disables direct DOM access from cross-site windows, then the attacker seems to be fairly limited in what she can do after replaying the navigation.

We should probably document all this... ;)

@anforowicz
Copy link

You're right, I probably shouldn't call my scenario "an attack" - the "attacker" has very little control over the victim's URL that the user is taken back to (and AFAIK ability to control parts of the URL is necessary to bisect information leaked in a cross-site-search attack).

@naskooskov
Copy link

I can see the point of view "get me back the same pixels" notion of back/forward navigations. But we can't do that reliably either when the server responds that the browser shouldn't cache the document. If caching is not prevented, I think history navigations just go to cache, so in those cases the headers discussion is somewhat moot. Overall I want to make sure we consider all the scenarios.

@csreis
Copy link

csreis commented Mar 29, 2019

I think I agree that session history navigations should preserve the original values for things like the request origin, so that it behaves the same on repeat visits. We've seen plenty of attacks where an attacker visits a victim page, leaves, and comes back to get different behavior (e.g., coordinated by another window), and that will likely get worse as we introduce controls like Sec-Fetch-Site.

I'm not too concerned about the cases where an attacker might try to time a back navigation to somewhere you've already been-- they don't have much control over the URL in that case. Same for cached pages, which basically show what was there on the previous visit rather than what you'd get if you visited the same URL without the requesting origin in use.

It's worth being explicit about what gets preserved, though, since it will likely need to be persisted to disk and restored into future sessions. Do we need more than the source document's origin? (I don't see how we would preserve a notion of the document.)

@terjanq
Copy link

terjanq commented Jun 26, 2020

I'd want to refresh the discussion about the issue. My perspective on the matter is very aligned with @anforowicz.

Current implementation in Chromium and Firefox, when navigated back through history.back(), replays the headers from original requests which in my opinion is an issue. With XS-Leaks, the attacker could potentially detect from external website on what page the victim is currently on (e.g. through window.length, window.history or cache probing attacks) and duplicate sensitive requests.

I created a PoC to imitate a simple attack on the user, that will duplicate the original request in the first section.

Another problems arises with location hash manipulations in the meantime. What if the attacker changes the URL fragment of the original window? In theory, exactly the same request goes to the server since the fragment will be ignored. Chrome will send cross-site in that scenario (section Location Hash in PoC), but Firefox sends none value, as reported in bug/1648825.

I think it's important to distinguish between browser and page initiated navigations.

@anforowicz
Copy link

anforowicz commented Jun 30, 2020

@terjanq, are you concerned about specific XS-Leaks that might open a way for additional attacks? I think that Chromium's stance (/cc @mikewest, @csreis) is that it might be impossible to fix _all* XS-Leaks in the browser, and therefore we may need websites to help with protecting themselves against XS-Leaks by refusing to serve sensitive content in presence of Sec-Fetch-Site: cross-site. If you can share specific XS-Leaks that you are concerned about, then it will be easier to evaluate if the leaks depend on the history behavior (or if the leaks would still exist even if we hypothetically prevented replaying the headers in history navigations).

FWIW, Chromium stores initiator_origin in FrameNavigationEntry since r677046 and persists the initiator during session restore since r703437. This moves Chromium behavior in the direction of: "take me back in time and show me the same pixels I saw".

@terjanq
Copy link

terjanq commented Jun 30, 2020

@anforowicz I am concerned that a cross-origin page can make a Sec-Fetch-Site: same-origin request from javascript. My PoC is just providing a scenario for:

You're right, I probably shouldn't call my scenario "an attack" - the "attacker" has very little control over the victim's URL that the user is taken back to (and AFAIK ability to control parts of the URL is necessary to bisect information leaked in a cross-site-search attack).

I mentioned XS Leaks, as well as implemented one in the PoC, to prove that although an attacker holds no control over the URL of the attacked website, they still can detect to what page the victim navigated to, and with that, duplicate sensitive requests.

FWIW, Chromium stores initiator_origin in FrameNavigationEntry since r677046 and persists the initiator during session restore since r703437. This moves Chromium behavior in the direction of: "take me back in time and show me the same pixels I saw".

In provided PoC, what will be the expected Sec-Fetch-Site now? cross-site?

@anforowicz
Copy link

an attacker [...] can detect to what page the victim navigated to

I am sorry, but I don't understand how that would happen - how would the attacked detect what page the victim navigated to? I see that in the PoC the cross-site popup can see (via opener.length) how many frames are present in the opener. I agree that this is a form of a XS-Leak. I disagree that it lets an attacker detect "what page the victim navigated to" (i.e. the URL nor page contents should not be disclosed to the cross-site attacker). I don't see how the PoC demonstrates that the cross-site frame sees "what page the victim navigated to".

I also don't understand how the history behavior discussed here (whether to replay some http request headers during history navigations) is related to the XS-Leak from the PoC. I think the you are arguing that bad things happen when 1) a victim page is served with Sec-Fetch-Site: same-origin and 2) an attacker can see victimWindow.length. It seems that both of these things can happen without a history navigation (e.g. an attacker can use window.open(victim) - the first navigation will have Sec-Fetch-Site: cross-site but subsequent same-origin navigations in the victim/popup would use Sec-Fetch-Site: same-origin.

In provided PoC, what will be the expected Sec-Fetch-Site now?

I would expect Sec-Fetch-Site: same-origin to be sent when navigating to https://terjanq.appspot.com/sec-fetch/sensitive... (twice, once after clicking a link, once after a history navigation).

@terjanq
Copy link

terjanq commented Jun 30, 2020

I disagree that it lets an attacker detect "what page the victim navigated to"

XSLeaks is unrelated to the core issue, so I didn't want to go into details on how to actually detect what page the victim navigated to. It can be done by fingerprinting the page using iframes. It doesn't disclose the URL of the page, just type of the page (e.g. OAuth page)

I also don't understand how the history behavior discussed here (whether to replay some http request headers during history navigations)

I am arguing that replaying headers can bypass server measurements, not necessarily against XS-Leaks, but in general for cross-origin requests. With a single user interaction, e.g. transferring money to the attacker, the attacker can duplicate the requests infinitely, from a cross-origin page.

In the PoC, only requests with Sec-Fetch-Site: same-origin will be accepted.

@domenic
Copy link
Member

domenic commented Apr 7, 2023

This was fixed by #6315: the initiator origin is now stored and tracked in https://html.spec.whatwg.org/#document-state-initiator-origin.

@domenic domenic closed this as completed Apr 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

8 participants