CORS Redirect to CORS Target contains Origin header in Preflight
Categories
(Core :: Networking: HTTP, defect, P2)
Tracking
()
People
(Reporter: chris.rodriguez1995, Unassigned, NeedInfo)
References
(Blocks 1 open bug)
Details
(Whiteboard: [necko-triaged][necko-priority-next])
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:104.0) Gecko/20100101 Firefox/104.0
Steps to reproduce:
I have a webpage on my domain, https://www.example.com.
I sent a request to an API that I host using a CORS request, https://api.example.com.
The CORS preflight to my API returned a status 200, and then the actual GET request to my API returned a 302, redirecting to another domain, in this case an Amazon CloudFront distribution, which is likewise hosted behind HTTPS, https://content.example.com.
The 302 triggers the browser to send a CORS request to https://content.example.com
Actual results:
Per the CORS spec, when that 302 happens, the subsequent GET request issued by the browser to https://content.example.com has the Origin header set to null for security / privacy purposes. However, the preceding CORS Preflight request still sends the Origin header with its original value instead of null.
This presents two issues:
- The first is the security issue that the null Origin issue was designed to mitigate in the first place, which I will not re-hash here.
- The second issue, which was what tipped me off to this problem, was that some hosts, like CloudFront, will send the host from the Origin header in the Access-Control-Allow-Origin header, and therefore the subsequent GET request will not be performed due to the fact that its null Origin doesn't match the value returned by the host. So, for example, the preflight would get back "Access-Control-Allow-Origin: www.example.com", but the browser wants to send the request using "Origin: null", so the request is always rejected due to missing Access-Control-Allow-Origin.
Expected results:
To me, the logical outcome here would have been the browser sending Origin: null in the CORS preflight. This would fix the security issue, and it would prevent the CORS origin mismatch that was causing me trouble.
However, I have not read the specs cover-to-cover like some, so there may be another answer that exists within the spec that I am not privy to.
Comment 1•2 years ago
|
||
Moving this to Core > Dom: Security, if this is not the right component, please move it to a more suitable one. Thanks!
Comment 2•2 years ago
|
||
We should check whether this happened in Firefox 102 or not (current ESR). Tom landed some CORS Origin: changes in 103 (I think) that may have affected this.
Updated•2 years ago
|
Reporter | ||
Comment 3•2 years ago
|
||
I have just tested and confirmed that this issue happens in Firefox 102.2.0 esr as well as Firefox 91.13.0 esr.
I have created a small test site that you can use to experiment with this using a random domain I happened to have sitting around: https://www.polyjuice.art
I made the backend emulate the behavior I was seeing from CloudFront as well, but the important part is the second CORS preflight that is sent.
Comment 4•2 years ago
|
||
This is so exceptionally useful, thank you for hosting this test case!
Reporter | ||
Comment 5•2 years ago
|
||
It's my pleasure! Please let me know if there's anything else I can do to help!
Comment 6•2 years ago
|
||
I've added a printf here where we set the Origin header.
Setting https://api.polyjuice.art/ origin https://www.polyjuice.art
Setting https://api.polyjuice.art/ origin https://www.polyjuice.art
Setting https://bpi.polyjuice.art/index.php origin null
Setting https://bpi.polyjuice.art/index.php origin https://www.polyjuice.art
The first one is triggered by the content process, second one by the main process.
It seems the main process doesn't properly set the principal to null on redirect here, probably because AsyncOnChannelRedirect
is not called for the CORS channel in the main process (I haven't yet figured out why that is).
In any case, we do seem to have a WPT for this and another one which seems concerning.
Though all browsers seem to be failing them so I'm not sure what to think.
Comment 7•2 years ago
|
||
Out of interested I commented out the code in nsCORSListenerProxy.cpp
that sets the Origin header, so that we only use the code in nsHttpChannel::SetOriginHeader
to set the Origin header. I think the result is quite interesting actually.
nsHttpChannel::SetOriginHeader [mURI = https://api.polyjuice.art/, method = GET]
Step 2: https://www.polyjuice.art
nsHttpChannel::SetOriginHeader [mURI = https://api.polyjuice.art/, method = OPTIONS]
Step 2: https://www.polyjuice.art
nsHttpChannel::SetOriginHeader [mURI = https://bpi.polyjuice.art/index.php, method = GET]
has a redirect-tainted origin
Step 2: null
nsHttpChannel::SetOriginHeader [mURI = https://bpi.polyjuice.art/index.php, method = OPTIONS]
Step 2: https://www.polyjuice.art
It seems like the GET request is redirect so it becomes tainted, but we do a new request for OPTIONS? (There is no guarantee of course that the redirect tainting code is correct of course, it's the part I understand the least myself)
Comment 8•2 years ago
|
||
Chrome may be failing the wpt tests also, but does it do something different (even if still wrong) in this particular instance? I could imagine Cloudflare doing whatever makes chrome work.
Reporter | ||
Comment 9•2 years ago
|
||
In this case, Chrome sends the preflight with the origin set to null correctly, and then as a result does not perform the request afterwards due to a CORS violation.
Something that's worth pointing out here for context is that the backend will emulate the behavior I was seeing from CloudFront, where sending a null Origin will result in not receiving any CORS headers back. This is because the CORS settings are configured to allow credentials, but you may not allow credentials on a request with a null origin, nor may you allow credentials if you have allowed hosts set to *
. The behavior I observed from CloudFront was not to just omit the Access-Control-Allow-Credentials
header, but instead to omit all CORS headers.
Comment 10•2 years ago
|
||
Not sure if the cause is the same, but I just reported bug 1800990 where there is also a disconnect between the Origin
header sent to servers and the "origin" header used internally.
As noted in the other bug, the Origin
used in CORS is simply computed from the triggeringPrincipal passed to nsCORSListenerProxy
(e.g. principal where fetch
is called), and sometimes null
when redirected.
The Origin
header sent to servers is computed elsewhere, in nsHttpChannel::SetOriginHeader
.
The two implementations differ significantly, and I would not be surprised if there are more cases where CORS falls apart, other than this bug and bug 1800990. I think that the Origin
request header should be read and used, instead of triggeringPrincipal, at https://searchfox.org/mozilla-central/rev/d7d2cc647772de15c4c5aa47f74d25d0e379e404/dom/security/nsContentSecurityManager.cpp#425
Updated•1 year ago
|
Updated•1 year ago
|
Updated•11 months ago
|
Updated•11 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Description
•