Giter Site home page Giter Site logo

Expand storage-access-preserving navigations to include same-origin-initiated navigations, not just self-initiated. about storage-access HOT 16 OPEN

bvandersloot-mozilla avatar bvandersloot-mozilla commented on September 17, 2024
Expand storage-access-preserving navigations to include same-origin-initiated navigations, not just self-initiated.

from storage-access.

Comments (16)

arturjanc avatar arturjanc commented on September 17, 2024 1

So, either we completely exclude nested same-site iframes from receiving storage access without rSA (or headers) or we always propagate the storage access bit to same-site children.

I'm leaning towards the latter. I haven't really seen clear evidence of a practical attack that would be possible against those nested iframes, especially one that isn't also applicable to any other same-site resource loaded by the original iframe. Without this kind of reasoning for restricting it, I think we should prefer developer utility and simplicity here.

Conceptually, I see this similarly to what @cfredric outlined above, i.e. it seems a bit cleaner for each document to require explicit calls to document.requestStorageAccess() to receive credentials when embedded in top-level-3P context. This way, having one document within a site that calls rSA doesn't suddenly make any content from its hosting site eligible to be embedded with credentials (if e.g. the document that received storage access directly iframes it).

OTOH, looking at this purely from a security perspective I think @johannhof is right that we don't have a compelling reason to disallow automatic propagation of storage access:

  • We preserve storage access on self-initiated navigations of the iframe, so it's already possible that a document that doesn't have rSA will have storage access in a 3P context.
  • Headers that restrict iframing (X-Frame-Options and frame-ancestors in CSP) check every document in the ancestor chain. So if there's a sensitive endpoint that doesn't want to be iframed cross-site, then even if it's unexpectedly embedded as a nested frame with storage access in a 3P context, it will still not render if it sets one of these headers.

One concern that @ddworken came up with is whether a cross-site ancestor, e.g. the top-level page, is permitted to navigate the nested frame (which, as @annevk points out above, is not well-defined) -- my hope is that it shouldn't. But if it was, then it could navigate that nested frame to a different endpoint same-site with the frame that requested storage access; so we would need to prevent this from happening, similarly to the self-initiated restriction we discussed above.

Overall, to me this boils down to the ergonomic benefit of automatically propagating storage access. Ideally, if this affects only a small number of widgets, we could forego this and require nested frames to call rSA. But if this introduces a burden on many developers building widget that request storage access, I think automatically propagating access to same-site frames would be okay.

from storage-access.

cfredric avatar cfredric commented on September 17, 2024 1

@jsnajdr the question of how CHIPS partition keys should be defined seems like it's best discussed in privacycg/CHIPS#88; let's discuss there, since that question seems somewhat unrelated to this issue.

(With that said, I agree with what @aselya said there. IMO it would be very confusing if an iframe's partition key could change over time; and the isolation that you're trying to achieve is still possible by simply not using partitioned storage.)

from storage-access.

bvandersloot-mozilla avatar bvandersloot-mozilla commented on September 17, 2024

@arturjanc: you had security concerns on the origin boundary w.r.t. navigations initiated by documents with storage access. Does this maintain the security invariants you wanted? I don't recall why the navigation was required to be self-initiated.

from storage-access.

arturjanc avatar arturjanc commented on September 17, 2024

This change makes sense to me and I don't think it will undermine the security properties we care about here.

The motivation behind the self-initiated restriction was to prevent a cross-origin embedder from navigating an iframe that received storage access to an arbitrary endpoint on the iframe's site chosen by the embedder (and e.g. clickjack it or leak data). But allowing same-origin-initiated navigations to maintain storage access is fine because it still protects the iframe from these kinds of navigations, so I think we can safely allow it.

from storage-access.

annevk avatar annevk commented on September 17, 2024

I think @johannhof was also interested in allowing this for the same site, no?

from storage-access.

arturjanc avatar arturjanc commented on September 17, 2024

Dumb question: can you even initiate a navigation in a same-site-but-cross-origin iframe if you're not the embedder? You generally shouldn't be able to navigate cross-origin frames except in a few cases, e.g. you have an embedder/embeddee relationship - I think we tightened this a few years ago, but don't remember exactly where we landed on that.

Basically, I'm not sure in which scenarios allowing same-site navigation would be useful here.

from storage-access.

annevk avatar annevk commented on September 17, 2024

That's not something that's currently well-defined unfortunately. whatwg/html#313 goes into some of it.

from storage-access.

jsnajdr avatar jsnajdr commented on September 17, 2024

Hi 👋 I'm the reporter of the Firefox bug that @bvandersloot-mozilla mentions. And the question whether storage access should be propagated to a nested same-site or same-origin iframe is alse very relevant to us.

Our setup looks like this:

  1. Third party sites embed an iframe from widgets.wordpress.com. This iframe requests storage access, and after it's granted, it expects to be able to issue credentialed fetch requests and load credentialed sub-iframes, with the unpartitioned login cookie the user got when logging into wordpress.com as a top-level site.
  2. The widgets.wordpress.com iframe loads a nested iframe, from public-api.wordpress.com. (Cross-origin, same-site). This nested iframe would like to be loaded with wordpress.com unpartitioned login cookie. This currently works on Chrome, but doesn't work on Firefox.
  3. The public-api.wordpress.com nested iframe would also like to send credentialed fetch requests to public-api.wordpress.com (same-origin). This currently doesn't work, neither in Chrome nor Firefox, because the storage access is not automatically propagated to the nested iframe. And it can't request storage access on its own, because there are no user interactions with this nested iframe. It's an invisible helper that communicates with its parent using messages.

Reading the discussion in this issue, it seems that propagating the storage access automatically to nested same-site iframes could be possible to standardize and implement? That would solve all our problems.

After all, if widgets.wordpress.com was a top-level site and it embedded public-api.wordpress.com, the iframe would get storage access, because:

If browsingContext is same authority with browsingContext’s top-level browsing context's active document, resolve p with true.

But when this tree of same-site iframes is embedded in a cross-origin document, and the top-level iframe of this tree is granted storage access, it's a similar situation, but the nested iframes don't get access.

from storage-access.

cfredric avatar cfredric commented on September 17, 2024

My concern with a change like this is that it makes it hard to "drop" unpartitioned cookie access once you've gotten it, even if you want to. Today, an iframe can drop cookie access just by opening a new iframe and not requesting access there. With this change, that's no longer possible. It seems like a step backward to the unsafe default of having unpartitioned cookie access without having asked for it, IMO.

Instead, I think a better solution for this is to use the Storage Access Headers proposal, specifically the load response header. Then the subdocument fetch will still be credentialed (since the parent iframe has unpartitioned cookie access, after all), and the fetch's response headers will indicate that the subresource iframe is opting into having unpartitioned cookie access, upon loading. This ensures that any iframe that wants cookie access still has to ask for it explicitly, which is a better default policy for security IMO.

@jsnajdr, I think there might be a misunderstanding on your third point - although storage access is not automatically propagated to the nested iframe, the nested iframe can call requestStorageAccess() without obtaining a user gesture first (at least in Chrome and Firefox), and the request will be granted since the user has already granted permission to the parent iframe. Can you give that a try?

from storage-access.

bvandersloot-mozilla avatar bvandersloot-mozilla commented on September 17, 2024

I like that approach, if we can get a 3-engine consensus on the storage access headers :)

from storage-access.

jsnajdr avatar jsnajdr commented on September 17, 2024

although storage access is not automatically propagated to the nested iframe, the nested iframe can call requestStorageAccess() [...] Can you give that a try?

Oh yes, this has been the missing piece and it works! And it works around the Firefox "bug" I reported. Initially, the frame load request is sent without unpartitioned cookies and the frame doesn't have storage access. But I can do this:

if (!await document.hasStorageAccess()) {
  await document.requestStorageAccess();
  window.location.reload();
}

After the access was granted, I reload the frame. This time the request is sent with unpartitioned cookies, and after the load the frame has storage access automatically, without having to ask for it.

With Storage Access Headers, the above script can be replaced with a response header Activate-Storage-Access: retry. Then the browser will do the same workflow automatically. However, I believe I still need to load the iframe twice, I don't see a way how to save the extra roundtrip.

It seems like a step backward to the unsafe default of having unpartitioned cookie access without having asked for it

I see, the design principle is that no embedded frame should automatically get storage access until it asks for it explicitly. The default is "no access".

But that means that Firefox does it right (not sending unpartitioned cookies on the initial load) and it's Chrome that has a bug. If the embedded frame doesn't have storage access by default, then the load request shouldn't have unpartitioned cookies. If it has them, it means that the frame effectively does have unpartitioned access, because the server response can contain data derived from the unpartitioned cookies.

from storage-access.

cfredric avatar cfredric commented on September 17, 2024

However, I believe I still need to load the iframe twice, I don't see a way how to save the extra roundtrip.

Yeah, there's a tradeoff between security and performance here. We're exploring that tradeoff space in privacycg/storage-access-headers#6; feel free to take a look and contribute if you have more ideas.

But that means that Firefox does it right (not sending unpartitioned cookies on the initial load) and it's Chrome that has a bug. If the embedded frame doesn't have storage access by default, then the load request shouldn't have unpartitioned cookies.

Maybe I'm misunderstanding, but from your initial description I understood that the embedded iframe does request storage access: This iframe requests storage access, and after it's granted, it expects to be able to issue credentialed fetch requests. One of the credentialed fetches that this iframe issues happens to be for the source of another iframe (which should not implicitly have storage access when it loads, IMO), but that doesn't change that the original iframe has storage access and can issue credentialed fetches. From that point of view, I think Chrome is behaving consistently here.

from storage-access.

jsnajdr avatar jsnajdr commented on September 17, 2024

One of the credentialed fetches that this iframe issues happens to be for the source of another iframe (which should not implicitly have storage access when it loads, IMO)

This is the core question that Firefox and Chrome are answering differently. Which iframe really does the fetch? Is it the parent iframe (and its document and window objects) or is it the child iframe?

Chrome thinks that the parent iframe does the fetch, doing it for the child iframe. Firefox thinks that the child frame does it, doing it for itself.

Is there a place in the HTML or Fetch standard that would state clearly which document owns the fetch? I would say it should be the child frame's contentDocument: that frame has its own "content navigable" and the fetch is part of its session history.

I noticed another difference between how Firefox and Chrome treat the iframe fetch differently. When there is a widgets.wordpress.com embedded frame that creates a nested public-api.wordpress.com iframe, the fetch has these headers in both browsers:

Sec-Fetch-Dest: iframe
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-site

Here, both browsers agree that the fetch is not same-origin, but same-site. It's like the parent frame owns the fetch. But when the frame reloads itself (in the "if doesn't have storage access, request access and reload" flow), then Chrome treats the second fetch as same-origin:

Sec-Fetch-Site: same-origin

But in Firefox it's still same-site, just like the initial request.

from storage-access.

johannhof avatar johannhof commented on September 17, 2024

As to "who does the fetch", I would defer to @annevk for authoritative knowledge of the current specification. In any case, I don't think we should be dogmatic about what the current spec says but consider what the best developer experience would be instead.

I agree that it's very confusing to have a credentialed document load and then no subsequent storage access in the document, since developers are likely to already make a decision about what content to render based on the server-side presence of cookies or not. It could also lead to subtle issues where most of the pre-rendered page works well but then subsequent credentialed subresource requests fail.

So, either we completely exclude nested same-site iframes from receiving storage access without rSA (or headers) or we always propagate the storage access bit to same-site children.

I'm leaning towards the latter. I haven't really seen clear evidence of a practical attack that would be possible against those nested iframes, especially one that isn't also applicable to any other same-site resource loaded by the original iframe. Without this kind of reasoning for restricting it, I think we should prefer developer utility and simplicity here.

from storage-access.

jsnajdr avatar jsnajdr commented on September 17, 2024

So, either we completely exclude nested same-site iframes from receiving storage access without rSA (or headers) or we always propagate the storage access bit to same-site children.

I was testing this with Safari (17.2.1) today and found that it already propagates the access to a nested same-site iframe:

  1. Top-level site example.com embeds iframe from widgets.wordpress.com
  2. widgets.wordpress.com requests access and it's granted. (On a second load it doesn't even need to request the access, it's auto-granted, which is nice). Then this iframe loads nested iframe from public-api.wordpress.com.
  3. public-api.wordpress.com has access (await document.hasStorageAccess() === true) right from the beginning, and doesn't need to request it.

However, there is a weird bug when wordpress.com is the top-level site. Then a nested public-api.wordpress.com iframe doesn't have storage access! It needs to ask for it with requestStorageAccess(), and additionally, that request needs to be triggered by a user interaction. Otherwise it rejects with undefined. I.e., what @cfredric recommends above:

although storage access is not automatically propagated to the nested iframe, the nested iframe can call requestStorageAccess() without obtaining a user gesture first (at least in Chrome and Firefox), and the request will be granted

doesn't work in Safari when a top-level frame (which never asked for access, because why would it when it's the top-level document) embeds a same-site iframe.

This is already reported in WebKit since 4 years ago, and @johnwilander responded "I made a review comment about this in the ongoing Storage Access API spec work." But there are no details about the location and the content of the review comment, and the bug is still there today.

from storage-access.

jsnajdr avatar jsnajdr commented on September 17, 2024

Following up on the discussion about whether an iframe load request should initially have storage access or not. There is a surprising relation to CHIPS and partitioned cookies, as pointed out in privacycg/CHIPS#88.

Depending on whether the request has storage access or not, a different set of cookies will be sent with it, and the server will send different Set-Cookie headers. What if the Set-Cookie cookies have the partitioned flag? Then both has-storage and no-storage iframes can share the same partition, and they will overwrite each other's cookies or info will leak from a has-storage to a no-storage iframe.

That means that the hasStorageAccess bit should be part of the partition key. Currently browsers implement a "has foreign ancestor" bit, but having a foreign ancestor is merely one of several ways how the same iframe embedded in a different way can sometimes have and sometimes not have storage access.

from storage-access.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.