这是indexloc提供的服务,不要输入任何密码
Skip to content

Embedded iframes with different storage access share (and overwrite) the same partitioned cookie #88

@jsnajdr

Description

@jsnajdr

At wordpress.com we tried to enable the Partitioned attribute on some of our auth cookies and then had to quickly revert the changes because we discovered the following problem.

At jardasn.dev I prepared a minimal reproduction example that shows what's going wrong. After visiting jardasn.dev and clicking "Login" you'll see a page with two embedded iframe. This is how it looks like in Chrome (with blocked 3rd party cookies):

Screenshot 2024-07-23 at 14 50 59

The site has a top-level login cookie called logged_in. And there are two iframes that embed a document from a same-site same-origin URL. One is embedded directly, the other is nested in an intermediate cross-origin iframe from jardasn-alt.dev.

When loading the jardasn.dev/embed document, the response includes a Set-Cookie header with another cookie, logged_in_rest. When the request has the logged_in cookie, then logged_in_rest value will be the same as logged_in, i.e., admin. When the request doesn't have logged_in cookie, the value will be anon. Both cookies are supposed to be in sync. The logged_in_rest cookie is partitioned, it's supposed to be "private" to the embed iframe. Think of it as an extra authentication token, or nonce, specific for the embed.

The second iframe is nested inside cross-origin jardasn-alt.dev iframe. That makes Chrome treat it as third party. Therefore the logged_in cookie will be blocked when the jardasn.dev/embed is loaded, and the iframe will not have storage access. The logged_in_rest cookie returned by the server is going to be anon.

The first iframe is logged in, the second is logged out. However, both iframes share the same partitioned storage! The second iframe loaded a bit later, and when storing the logged_in_rest=anon cookie, it set it also for the first iframe! If you click the "Reread document.cookie" button in the first iframe, you'll see that the value changes from admin to anon:

Screenshot 2024-07-23 at 15 05 16

That's because the second iframe stored a new value. This is a big problem because instead of having one frame fully logged in and another fully logged out, the iframes pollute each other's cookies and the first frame becomes half logged out.

Let's send a REST request from both iframes. Clicking the "Send REST request" will trigger a same-origin fetch() and in the response the server will tell us which cookies did the request have:

Screenshot 2024-07-23 at 15 10 09

The first iframe's request cookies are out of sync. logged_in (first party cookie) is admin, but the logged_in_rest is anon, like if the iframe was logged out.

The second iframe's request cookie are OK. The logged_in one is missing because it's a first party cookie and it's blocked because there's no storage access. And the partitioned logged_in_rest cookie also says the request is anonymous.

If the first iframe loaded second, it would lead to a similar situation where the second frame is broken. It wouldn't have a valid logged_in cookie available, but it would have a valid logged_in_rest=admin. The login information would leak into the logged out iframe!

The root cause of all this is that both iframes share the same partition key, while having different first-party cookie access.

Firefox behaves differently. (I use the Nightly version with optInPartitioning enabled) Apparently both cookies are stored with different partition keys because they have two independent values:

Screenshot 2024-07-23 at 15 17 43

The first frame is fully logged in, the second is fully logged out, each has its own logged_in_rest cookie and clicking "Re-read document.cookie" doesn't change the value. Both REST requests have two cookies that are in sync.

Safari also behaves differently. It doesn't support partitioned cookies, but the key difference is that both iframes do have storage access, so they both see the same first party logged_in and logged_in_rest cookies:

Screenshot 2024-07-23 at 15 21 19

It seems that Safari doesn't care about the intermediate cross-origin jardasn-alt.dev iframe and that it gives the nested iframe full storage access anyway.

It's only Chrome where the problematic situation happens: shared partitioned storage, different storage access.

The second iframe can "fix itself" by requesting storage access and immediately reloading:

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

The iframe is already "entitled" to have storage access, it just has to ask for it. And the request is exempt from the usual limitations -- doesn't need to be triggered by user gesture, doesn't show a popup.

But anyway, while the iframe is reloading, the bad logged_in_rest cookie is still in the shared storage and can cause damage. Only after it reloads it sets the right cookie and both iframes are logged in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions