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

Allow Origin and Access-Control-Allow-Origin to have multiple values #1790

Open
arulthileeban opened this issue Dec 5, 2024 · 4 comments
Open
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: cors

Comments

@arulthileeban
Copy link

arulthileeban commented Dec 5, 2024

Context

Identity Aware Proxies(IAP) form the core of most Zero Trust solutions including Google BeyondCorp, Cloudflare Access, Okta Access Gateway, Pomerium. An IAP is a VPN replacement that acts as a reverse proxy requiring user authorization context to allow access to the sites it fronts. It retrieves this context by requiring a user to authenticate and authorize themselves, upon which the context is created and provided to the client in the form of a cookie included with each request to the IAP.

A sample flow for user accessing site1.com fronted by an IAP is as follows:

iap_flow

When a user without any existing session accesses a site fronted by an IAP, the IAP requires the user to authenticate, creating an authentication session first. It then creates and sets an authorization cookie specifically for the target site. After this, if the user accesses a second site fronted by the IAP, it uses the existing authentication session and creates a new authorization cookie for the second site. Typically, the onboarded sites (Site1, Site2) have their DNS records pointed to the IAP, which proxies traffic based on the Host header or SNI. After the initial authentication, this process is completely transparent to the user. For L7 traffic, the user experience is similar to accessing target sites through a VPN in most cases.

Issue

If a user accesses Site 1 and gets a cookie set for Site 1, and then Site 1 attempts to make an Fetch/XHR request to Site 2 (which does not yet have a cookie) proxied by the IAP, this becomes an issue due to CORS. Traditionally, this scenario works seamlessly with a VPN. In this case, since the initial authentication for Site 1 creates an authentication session for the user, the browser simply needs to follow redirects to Site 2 to get a cookie set for Site 2. While Fetch/XHR requests do follow redirects, they run into CORS issues due to Origin value being set to null on redirects as follows:

cors_current

An ideal flow would look like this and would remedy this situation:

cors_fixed

* Please note that the flow diagrams include only specific headers and not all required headers

These issues have been documented over a long period of time in the IAP space, with non-standard workarounds suggested:

The workarounds so far have been:

  • Ask users to access Site 2 first: Users access Site 2 before Site 1 to ensure the cookie for Site 2 is set.
  • Set a cookie across the subdomain: Configure cookies to be shared across all endpoints fronted by the IAP, by specifying it for a subdomain instead of per site. This removes a security barrier foundational to Zero Trust principles and won’t work in cases where there are multiple subdomains involved.
  • Share the cookie via a header: Modify applications to include the cookie in headers. This adds complexity and deviates from standard practices.

These workarounds are useful only in limited scenarios and makes the user and application onboarding experience difficult compared to a traditional VPN.This also limits the rollout of these Zero Trust solutions due to the impact in user workflows. With the industry moving towards Zero Trust solutions, it’d be beneficial to solve this issue directly.

Potential solution

Currently, the issue is caused by cross origin redirects setting Origin value to “null”, which is done to prevent confused deputy attacks like CSRF during redirects. Unfortunately, this is causing issues in scenarios like this. I would welcome any solution that helps mitigate this issue; the easiest and most secure way to resolve this would be to allow the Origin and Access-Control-Allow-Origin headers to have multiple values. This approach will help construct a trusted path, mitigating confused deputy attacks and removing the anonymity from redirects as showcased here:

cors_fixed_multiple

There is precedent with the original Web Origin Concept RFC mentioning this:

In those cases, the user agent MAY list all the origins in the Origin header field. For example, if the HTTP request was initially issued by one origin but then later redirected by another origin, the user agent MAY inform the server that two origins were involved in causing the user agent to issue the request.

The CORS documentation for developers also discusses how the Access-Control-Allow-Origin header was intended to have multiple origin values:

Although the CORS specification implies that you can list multiple origins in the Access-Control-Allow-Origin header, in practice only a single value is allowed by all modern browsers.

If this could cause backwards compatibility issues, could we consider adding a new value to the redirect parameter in fetch in addition to “follow”, “manual” and “error” : “follow-cors”?

@annevk annevk added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Dec 6, 2024
@annevk
Copy link
Member

annevk commented Dec 6, 2024

I don't understand how this setup works with third-party cookie deprecation.

Provided it somehow works, we can't change how Origin works without opt-in. The original RFC has long been considered obsolete. (There's a desire to allow multiple values for Access-Control-Allow-Origin, but that would mainly be as a cache benefit and would not help with this case. I think if we decided to pursue this case we'd use a space-separated value, not comma-separated.)

@arulthileeban
Copy link
Author

arulthileeban commented Dec 12, 2024

I don't understand how this setup works with third-party cookie deprecation.

Just to clarify, site1.com, site2.com, and auth.com set cookies for themselves through redirects. There is no iap.com in the view of the browser, since it is only used for resolution. There was a small error in the first flow diagram, which I fixed to clear it up.

The flow that is problematic with third-party cookie deprecation is when Site 1 makes a CORS request to Site 2, requiring a third party cookie. This likely works currently because of a few reasons:

  • IAP is used in enterprises for internal resource access, where there are limited second level domains. Most CORS requests likely occur within the eTLD+1 domain space, and hence not considered third-party cookies (eg: app.xyz.com -> api.xyz.com -> auth.xyz.com).

  • Browsers that have “tracking prevention policy” block third-party cookies only when set in third-party contexts without user intent, which is not the case with the workaround where the user accesses Site B first and then Site A. The other two workarounds don’t have the third party cookie problem.

  • As documented here, enterprises probably add exceptions to this since they’d typically manage browser settings through MDMs.

While the last two measures won’t help in the long term, and IAP architectures may need to evolve as the new PrivacyCG standards mature (eg: Partitioning target site cookies), the core issue remains: Browsers cannot properly follow redirects during a CORS request without forcing applications to rely on a wildcard Access-Control-Allow-Origin.

As mentioned above, this limitation even restricts credentialed CORS redirects between subdomains within an eTLD+1 domain, which third-party cookie deprecation does not affect.

Provided it somehow works, we can't change how Origin works without opt-in. The original RFC has long been considered obsolete. (There's a desire to allow multiple values for Access-Control-Allow-Origin, but that would mainly be as a cache benefit and would not help with this case. I think if we decided to pursue this case we'd use a space-separated value, not comma-separated.)

This is reasonable. Does my suggestion in the initial description help as an opt-in strategy: Add another parameter to fetch redirect “follow_cors”? Setting this would cause the browser to create Multiple Origins for the request upstream and since this is always going to be in a cross origin context, the server should be aware of the multiple origins, else it won’t set the appropriate Access-Control-Allow-Origin headers causing failure.

@annevk
Copy link
Member

annevk commented Dec 13, 2024

I suspect so.

Now in 2009 I had actually written up things as a space-separated list and Adam Barth followed that in the IETF draft: https://lists.w3.org/Archives/Public/public-webapps/2009JulSep/1267.html.

However, I undid this here because implementations were not emitting a list: 713ddad.

One of the URLs referenced there has this quote from Brad Hill:

This is necessary to prevent reflection vulnerabilities in which the target of an XHR causes a redirect back to the origin making the request.

So that's a case of A making a request to B which redirects to A. Today that gives Origin: null to the final A. Historically that would have given Origin: A B. Either way that seems sufficient to me security-wise. If we do anything here we probably have to better understand why there was a convergence on null to begin with.

@arulthileeban
Copy link
Author

arulthileeban commented Dec 14, 2024

I reached out to Brad and he provided some relevant references to this discussion:

Further, I dug into the browsers' code history to see if there was anything there:

  • Firefox's change occurred after the Fetch Spec change 713ddad. Prior to that change, their Origin documentation linked in the report which triggered your spec change was not aligned with their implementation.
  • Chromium's change was reported as a bug and patched after most browsers had adopted this behavior.
  • Webkit's change came from this bug, which I don't have access to. A duplicate of that bug has some answers for us:

RFC6454 allows this header to be multi-valued on redirects, but CORS (http://w3.org/TR/cors) implicitly requires it to be single-valued (because it specifies an exact string match comparison)

At that point, CORS spec seems to rely on HTML5 for definition of Origin, which defines it implicitly as single valued. Piecing it together with the Firefox Origin documentation clarifies why they could have arrived at "null" for that single value.

Just by reading between the lines, it doesn't seem like there was a coordinated pathway to this convergence. Instead it looks like it was driven by implementers not using multiple origins for redirects causing the vulnerability you referenced, and variations between different specifications at that point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: cors
Development

No branches or pull requests

2 participants